ARC121F 题解

First Post:

Last Update:

本题一开始读错题了,把题目加强了好多。(

本题有经典的去叶子归纳法,值得学习。

考虑知道了标号之后怎么判断合法。

考虑一个叶子和其连向父亲的边。

如果边是 OR,点是 ,原问题一定有解。

如果边是 OR,点是 ,或者边是 AND,点是 ,那这个叶子对答案没有影响,可以直接舍弃。

如果边是 AND,点是 ,要尽量早的把这个叶子缩掉,这样对后面的危害最小。

把这个步骤由深往浅的归纳,就会发现,我们对一棵子树只需要记录两个信息:最终的权值,以及有没有一个连通块向外恰好连了一条 OR 边 且最终权值为

说白了就是记录在整个过程中,有没有出现 "OR 1" 型的叶子。

设出状态 表示 子树的最终权值为 / 为 且没有 "OR 1" 型叶子 / 有 "OR 1" 型叶子。

那么状态转移如下: 第一行: and or and and

第二行: 因为没有 "OR 1" 型叶子, 子树必须是 "AND 1" 或者 "OR 0",那么 所属连通块权值必然为

第三行:前面是任意一侧有 "OR 1" 型叶子的情况,后面是 子树是 "OR 1" 型叶子的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5,P = 998244353;
inline int Add(int a,int b) { return (a + b >= P) ? (a + b - P) : (a + b);}
int n;
vector<int> G[N];
int f[N][3];
void dp(int x,int fa)
{
f[x][0] = f[x][1] = 1;
for(auto y : G[x])
{
if(y == fa) continue;
dp(y,x);
int v0 = f[x][0],v1 = f[x][1],v2 = f[x][2];
f[x][2] = Add(2ll * v2 * Add(f[y][2],Add(f[y][0],f[y][1])) % P,Add(2ll * f[y][2] * (v0 + v1) % P,1ll * f[y][1] * (v0 + v1) % P));
f[x][0] = Add(Add(2ll * v0 * f[y][0] % P , 1ll * v0 * f[y][1] % P),1ll * v1 * f[y][0] % P);
f[x][1] = 1ll * v1 * (f[y][1] + f[y][0]) % P;
}
}
int main()
{
cin >> n;
for(int i = 1;i < n;i++)
{
int x,y;
cin >> x >> y;
G[x].push_back(y);
G[y].push_back(x);
}
dp(1,0);
cout << Add(f[1][1],f[1][2]) << endl;
return 0;
}