题解:[ARC121F] Logical Operations on Tree

题目传送门

题意分析

显然树形 DP。

对于父节点 \(x\) 和子节点 \(v\) 之间的连边,考虑把 \(v\) 的信息合并到 \(x\) 上。

  • 如果边是 \(\operatorname{or}0,\operatorname{and}1\),则没有影响,直接继承 \(f_{v,0}+f_{v,1}\) 种方案。
  • 如果边是 \(\operatorname{and}0\),父节点一定会变成 \(0\),方案数 \(f_{v,0}\)
  • 如果是 \(\operatorname{or}1\),则可以发现此时全部合并到 \(v\) 上即为答案,一定有解

第三种需要特殊判断,设 \(f_{x,0},f_{x,1}\) 分别表示子树内全部收缩到 \(x\),点权为 \(0,1\) 的方案数;\(f_{x,2}\) 表示子树内存在 \(\operatorname{or}1\) 的方案数。

转移不难把 \(v\) 合并到 \(x\) 上(记转移前 \(f\)\(f'\)):

\[\begin{aligned} f_{x,0}&\leftarrow f'_{x,0}(f_{v,0}+f_{v,1})+(f'_{x,0}+f'_{x,1})f_{v,0}\\ f_{x,1}&\leftarrow f'_{x,1}(f_{v,0}+f_{v,1})\\ f_{x,2}&\leftarrow (f'_{x,0}+f'_{x,1})f_{v,1}+2(f'_{x,0}+f'_{x,1})f_{v,2}+2f'_{x,2}(f_{v,0}+f_{v,1}+f_{v,2}) \end{aligned} \]

带一个系数 \(2\) 是因为已经有解,\(\operatorname{and},\operatorname{or}\) 随便。

初始状态即 \(f_{x,0}=f_{x,1}=1,f_{x,2}=0\)

AC 代码

//#include<bits/stdc++.h>
#include<algorithm> 
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
#define int long long 
constexpr const int N=1e5,P=998244353;
int n,f[N+1][3];
vector<int>g[N+1];
void dfs(int x,int fx){
	f[x][0]=f[x][1]=1;
	f[x][2]=0;
	for(int v:g[x]){
		if(v==fx){
			continue;
		}
		dfs(v,x);
		int f0[3]={f[x][0],f[x][1],f[x][2]};
		f[x][0]=(f0[0]*(2ll*f[v][0]%P+f[v][1])%P+1ll*f0[1]*f[v][0]%P)%P;
		f[x][1]=1ll*f0[1]*(f[v][0]+f[v][1])%P;
		f[x][2]=((f0[0]+f0[1])%P*(2ll*f[v][2]+f[v][1])%P+2ll*f0[2]*(f[v][0]+f[v][1]+f[v][2])%P)%P;
	}
}
main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	cin>>n;
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	dfs(1,0);
	cout<<(f[1][1]+f[1][2])%P<<'\n';
	
	cout.flush(); 
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}
posted @ 2026-07-23 15:34  TH911  阅读(1)  评论(0)    收藏  举报