P7727 风暴之眼 Eye of the Storm (树形 DP)
谨
以
此
文
表
达
笔
者
个
人
观
点
,
如
有
冒
犯
官
解
,
可
在
评
论
区
诉
说
_{^{_{谨以此文表达笔者个人观点,如有冒犯官解,可在评论区诉说}}}
谨以此文表达笔者个人观点,如有冒犯官解,可在评论区诉说
题面
天体风暴中的气象瞬息万变。
风暴中的道路构成一棵
n
n
n 个结点的无根树,第
i
i
i 个结点有初始权值
w
i
w_i
wi(
w
i
w_i
wi 为
0
0
0 或
1
1
1)和类型
t
i
t_i
ti。
结点的类型分为两种:
AND
\texttt{AND}
AND 型结点和
OR
\texttt{OR}
OR 型结点。
对于
AND
\texttt{AND}
AND 型结点,每一秒结束后它的权值将变为它与它所有邻居上一秒权值的
AND
\texttt{AND}
AND 和;
对于
OR
\texttt{OR}
OR 型结点,每一秒结束后它的权值将变为它与它所有邻居上一秒权值的
OR
\texttt{OR}
OR 和。
现在,已知从某一时刻起,所有结点的权值都不再发生任何变化,将此时点
i
i
i 的权值称为
a
i
a_i
ai。
现不知每个点的初始权值和类型,只知道最终每个点的权值
a
i
a_i
ai,求出有多少种可能的初始权值和类型的组合,答案对
998244353
998244353
998244353 取模。
2
≤
n
≤
2
×
1
0
5
,
1000
m
s
,
256
M
B
2\leq n\leq2\times10^5~~,~~1000\,{\tt ms}~~,~~256\,{\tt MB}
2≤n≤2×105 , 1000ms , 256MB .
题解
我们可以发现,
A
N
D
\tt AND
AND 类型一但接触到 0,就会保持 0 永久不变,
O
R
\tt OR
OR 类型一旦接触到 1,就会保持 1 永久不变。所以,最终的情况,一定是由权值为 1 的
O
R
\tt OR
OR 、权值为 0 的
A
N
D
\tt AND
AND 、四周全是 1 的
A
N
D
\tt AND
AND 和 四周全是 0 的
O
R
\tt OR
OR 组成。
我讲的尽量形象一点吧。
我们先看 0 和 1 的交界处,它们比较特别。
这些地方能一直保持这种 “剑拔弩张” 的状态,想必交界处的两个点不简单。简单假设推理一下就会发现,边界处的 1 一定是
O
R
\tt OR
OR 形,因为它们是 “亲 1” 的,能守其 1,义不变 0,如果是
A
N
D
\tt AND
AND 形就守不住 1 了,和紧挨着的 0 只用接触一回合,就永远变成 0 了。同理,0 那边一定是
A
N
D
\tt AND
AND 形的 0 了。
我们不妨把这两种结点称之为 “哨兵”,每一个 1 的连通块、或者 0 的连通块边上(边上指的是0,1交界处)都必须有哨兵把守。当然,这并不代表连通块内部就没有哨兵了。
类似的,1-连通块内的
A
N
D
\tt AND
AND 、0-连通块内的
O
R
\tt OR
OR 这两种朝三暮四的结点,不妨把它们称之为 “庶民” 。
一开始,并不一定每个哨兵都是自己应该的权值(1-连通块内为 1,0-连通块内为 0),它们可能 “未激活”(呈现为敌方的权值),一个未激活的哨兵是可以被身边的 “亲和权值” 给感染、然后激活的,这其中包括身边的激活的友方哨兵(出于呼唤)或未激活的敌方哨兵(出于警觉)。
但是,庶民的摇摆不定就决定了,任何时刻,庶民和 “敌对势力” 或者未激活的友方哨兵(毕竟带有敌方的权值)中间都必须有激活的哨兵相隔。
这么想,那么最初就可能是这样:每个连通块内有若干个小块,这些小块周围是一圈激活的哨兵,内部是哨兵或庶民,连通块内除了这些小块以外的地方,全是未激活的哨兵。随着时间推移,未激活的哨兵渐渐全部激活,最终形成输入中的模样。
但对于每个连通块,还有一种情况:一开始就全是未激活的哨兵!庶民不识抬举,要他们何用 这种情况,连通块四周一定得有至少一个敌方的未激活哨兵相邻才行,不然会被围剿死 不然就全都激活不了了。
好,接下来,就可以着手设计 DP 了。
同权值的连通块一开始是已知的。每个点最初的状态有三种 :未激活的哨兵,激活的哨兵,庶民。分别用 1,2,3 型表示吧,我们设计
d
p
1
[
i
]
dp1[i]
dp1[i],
d
p
2
[
i
]
dp2[i]
dp2[i],
d
p
3
[
i
]
dp3[i]
dp3[i] 表示结点
i
i
i 先不考虑父亲的情况下为 1、2、3 型时,
i
i
i 子树的方案数。
但是对于一开始就全是未激活哨兵的情况不好处理,于是我们就令
d
p
1
[
i
]
[
1
]
dp1[i][1]
dp1[i][1] 为原先的
d
p
1
[
i
]
dp1[i]
dp1[i] ,令
d
p
1
[
i
]
[
0
]
dp1[i][0]
dp1[i][0] 为
i
i
i 结点所在连通块(暂不考虑其父亲)全是未激活哨兵、并被围剿死的情况下,子树的方案数,也就是不合法的方案数。
这四者初始值都为 1,因为要做乘法。最后
d
p
1
[
i
]
[
1
]
dp1[i][1]
dp1[i][1] 要减去
d
p
1
[
i
]
[
0
]
dp1[i][0]
dp1[i][0] ,即减去不合法。
如果扫描到某个儿子
j
j
j,与自己在同一个连通块内(
a
i
=
a
j
a_i=a_j
ai=aj),那么:
- d
p
1
[
i
]
[
0
]
←
d
p
1
[
i
]
[
0
]
∗
d
p
1
[
j
]
[
0
]
dp1[i][0]\leftarrow dp1[i][0]*dp1[j][0]
dp1[i][0]←dp1[i][0]∗dp1[j][0] ,这个很明显,儿子也得被围剿才行。
- d
p
1
[
i
]
[
1
]
←
d
p
1
[
i
]
[
1
]
∗
(
d
p
1
[
j
]
[
0
]
+
d
p
1
[
j
]
[
1
]
+
d
p
2
[
j
]
)
dp1[i][1]\leftarrow dp1[i][1]*(dp1[j][0]+dp1[j][1]+dp2[j])
dp1[i][1]←dp1[i][1]∗(dp1[j][0]+dp1[j][1]+dp2[j]) ,这是还未减去不合法情况下的
d
p
1
[
i
]
[
1
]
dp1[i][1]
dp1[i][1] ,所以当然要兼容并包,同时未激活的哨兵可以紧挨着激活的哨兵,所以要算上
d
p
2
[
j
]
dp2[j]
dp2[j] 。
- d
p
2
[
i
]
←
d
p
2
[
i
]
∗
(
d
p
1
[
j
]
[
0
]
+
d
p
1
[
j
]
[
1
]
+
d
p
2
[
j
]
+
d
p
3
[
j
]
)
dp2[i]\leftarrow dp2[i]*(dp1[j][0]+dp1[j][1]+dp2[j]+dp3[j])
dp2[i]←dp2[i]∗(dp1[j][0]+dp1[j][1]+dp2[j]+dp3[j]) ,已经激活的哨兵在连通块内,说明不符合“全是未激活哨兵” 这种假设了,儿子认为的被围剿死的方案 (
d
p
1
[
j
]
[
0
]
dp1[j][0]
dp1[j][0]),到父亲这儿又行了。同时,激活的哨兵身边什么人都可以有,自然就全盘转移过来。
- d
p
3
[
i
]
←
d
p
3
[
i
]
∗
(
d
p
2
[
j
]
+
d
p
3
[
j
]
)
dp3[i]\leftarrow dp3[i]*(dp2[j]+dp3[j])
dp3[i]←dp3[i]∗(dp2[j]+dp3[j]) ,庶民身边只能是庶民或激活的哨兵,这个不必细说。
如果扫描到某个儿子
j
j
j 与自己在不同连通块的话(
a
i
≠
a
j
a_i\not=a_j
ai=aj):
- d
p
1
[
i
]
[
0
]
←
d
p
1
[
i
]
[
0
]
∗
d
p
2
[
j
]
dp1[i][0]\leftarrow dp1[i][0]*dp2[j]
dp1[i][0]←dp1[i][0]∗dp2[j] ,既然被对方围剿,那就是和激活的敌方哨兵相邻了。
- d
p
1
[
i
]
[
1
]
←
d
p
1
[
i
]
[
1
]
∗
(
d
p
1
[
j
]
[
0
]
+
d
p
1
[
j
]
[
1
]
+
d
p
2
[
j
]
)
dp1[i][1]\leftarrow dp1[i][1]*(dp1[j][0]+dp1[j][1]+dp2[j])
dp1[i][1]←dp1[i][1]∗(dp1[j][0]+dp1[j][1]+dp2[j]) ,对方可以是激活或未激活的哨兵,这个不用说。主要是
d
p
1
[
j
]
[
0
]
dp1[j][0]
dp1[j][0] ,由于父亲是未激活的哨兵,儿子认为的被围剿死的方案,因父亲放了生路,又行了。
- d
p
2
[
i
]
←
d
p
2
[
i
]
∗
(
d
p
1
[
j
]
[
1
]
+
d
p
2
[
j
]
)
dp2[i]\leftarrow dp2[i]*(dp1[j][1]+dp2[j])
dp2[i]←dp2[i]∗(dp1[j][1]+dp2[j]) ,激活的哨兵,儿子被围剿就是被围剿,和上面相比,不能转移过来。
- d
p
3
[
i
]
←
0
dp3[i]\leftarrow 0
dp3[i]←0 ,庶民怎么能跑到边境呢?直接清零。
最后再来一个
d
p
1
[
i
]
[
1
]
←
(
d
p
1
[
i
]
[
1
]
−
d
p
1
[
i
]
[
0
]
)
dp1[i][1]\leftarrow (dp1[i][1]-dp1[i][0])
dp1[i][1]←(dp1[i][1]−dp1[i][0]) 。
至此,转移就理清了,最终答案是
d
p
1
[
R
o
o
t
]
[
1
]
+
d
p
2
[
R
o
o
t
]
+
d
p
3
[
R
o
o
t
]
dp1[Root][1]+dp2[Root]+dp3[Root]
dp1[Root][1]+dp2[Root]+dp3[Root]。时间复杂度
O
(
n
)
O(n)
O(n) 。
CODE
精炼的代码
#include<map>
#include<set>
#include<stack>
#include<queue>
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 200005
#define ENDL putchar('\n')
#define LL long long
#define DB double
#define lowbit(x) ((-x) & (x))
LL read() {
LL f = 1,x = 0;char s = getchar();
while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
return f * x;
}
const int MOD = 998244353;
int n,m,i,j,s,o,k;
vector<int> g[MAXN];
int inv[MAXN];
int a[MAXN];
int dp1[MAXN][2],dp2[MAXN],dp3[MAXN]; // 1,2,3
void dfs(int x,int ff) {
dp1[x][0] = dp2[x] = dp3[x] = 1;
dp1[x][1] = 1;
for(int i = 0;i < (int)g[x].size();i ++) {
int y = g[x][i];
if(y != ff) {
dfs(y,x);
if(a[y] == a[x]) {
dp1[x][1] = (0ll+dp1[y][0] + dp1[y][1] + dp2[y]) % MOD *1ll* dp1[x][1] % MOD;
dp1[x][0] = dp1[y][0] *1ll* dp1[x][0] % MOD;
dp2[x] = (0ll+dp1[y][0]+dp1[y][1] + dp3[y] + dp2[y]) % MOD *1ll* dp2[x] % MOD;
dp3[x] = (dp2[y] + dp3[y]) % MOD *1ll* dp3[x] % MOD;
}
else {
dp1[x][1] = (0ll+dp1[y][1] + dp1[y][0] + dp2[y]) % MOD *1ll* dp1[x][1] % MOD;
dp1[x][0] = (dp2[y]) % MOD *1ll* dp1[x][0] % MOD;
dp2[x] = (dp1[y][1] + dp2[y]) % MOD *1ll* dp2[x] % MOD;
dp3[x] = 0;
}
}
}
(dp1[x][1] += MOD-dp1[x][0]) %= MOD;
return ;
}
int main() {
n = read();
for(int i = 1;i <= n;i ++) {
a[i] = read();
}
inv[0] = inv[1] = 1;
for(int i = 2;i <= n;i ++) {
inv[i] = (MOD-inv[MOD % i]) *1ll* (MOD/i) % MOD;
}
for(int i = 1;i < n;i ++) {
s = read();o = read();
g[s].push_back(o);
g[o].push_back(s);
}
dfs(1,0);
int ans = (0ll+dp1[1][1]+dp2[1]+dp3[1]) % MOD;
printf("%d\n",ans);
return 0;
}
P7727 风暴之眼 Eye of the Storm (树形 DP)的更多相关文章
- poj3417 LCA + 树形dp
Network Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 4478 Accepted: 1292 Descripti ...
- COGS 2532. [HZOI 2016]树之美 树形dp
可以发现这道题的数据范围有些奇怪,为毛n辣么大,而k只有10 我们从树形dp的角度来考虑这个问题. 如果我们设f[x][k]表示与x距离为k的点的数量,那么我们可以O(1)回答一个询问 可是这样的话d ...
- 【BZOJ-4726】Sabota? 树形DP
4726: [POI2017]Sabota? Time Limit: 20 Sec Memory Limit: 128 MBSec Special JudgeSubmit: 128 Solved ...
- 树形DP+DFS序+树状数组 HDOJ 5293 Tree chain problem(树链问题)
题目链接 题意: 有n个点的一棵树.其中树上有m条已知的链,每条链有一个权值.从中选出任意个不相交的链使得链的权值和最大. 思路: 树形DP.设dp[i]表示i的子树下的最优权值和,sum[i]表示不 ...
- 树形DP
切题ing!!!!! HDU 2196 Anniversary party 经典树形DP,以前写的太搓了,终于学会简单写法了.... #include <iostream> #inclu ...
- BZOJ 2286 消耗战 (虚树+树形DP)
给出一个n节点的无向树,每条边都有一个边权,给出m个询问,每个询问询问ki个点,问切掉一些边后使得这些顶点无法与顶点1连接.最少的边权和是多少.(n<=250000,sigma(ki)<= ...
- POJ2342 树形dp
原题:http://poj.org/problem?id=2342 树形dp入门题. 我们让dp[i][0]表示第i个人不去,dp[i][1]表示第i个人去 ,根据题意我们可以很容易的得到如下递推公式 ...
- hdu1561 The more, The Better (树形dp+背包)
题目链接:http://acm.split.hdu.edu.cn/showproblem.php?pid=1561 思路:树形dp+01背包 //看注释可以懂 用vector建树更简单. 代码: #i ...
- bzoj2500: 幸福的道路(树形dp+单调队列)
好题.. 先找出每个节点的树上最长路 由树形DP完成 节点x,设其最长路的子节点为y 对于y的最长路,有向上和向下两种情况: down:y向子节点的最长路g[y][0] up:x的次长路的g[x][1 ...
随机推荐
- Arraylist集合、对象数组
Arraylist集合 ArrayList是List接口的一个实现类,它是程序中最常见的一种集合. 他的特点:在增加或删除指定位置的元素时,会创建新的数组,效率比较低,因此不适合做大量的增删操作,Ar ...
- [WUSTCTF2020]颜值成绩查询-1
分享下自己在完成[WUSTCTF2020]颜值成绩查询-1关卡的手工过程和自动化脚本. 1.通过payload:1,payload:1 ,payload:1 or 1=1--+,进行判断是否存在注入, ...
- 获得MySQL数据库存放位置
更新记录 2022年6月13日 发布. 2022年6月11日 开始. 通过查看MySQL与存储目录相关的参数 show variables like '%dir%'; 通过查询后datadir参数的值 ...
- 【Java面试】TCP协议为什么要设计三次握手?
一个工作5年的粉丝,最近去面试了很多公司,每次都被各种技术原理题问得语无伦次. 由于找了快1个月时间的工作,有点焦虑,来向我求助. 我能做的只是保证每天更新一个面试题,然后问他印象最深刻的一个面试题是 ...
- JavaScript有哪些数据类型,它们的区别?
基本数据类型:number.string.boolean.Undefined.NaN(特殊值).BigInt.Symbol 引入数据类型:Object NaN是JS中的特殊值,表示非数字,NaN不是数 ...
- 一次 Keepalived 高可用的事故,让我重学了一遍它!
原文首发: 你好,我是悟空. 前言 上次我们遇到了一个 MySQL 故障的事故,这次我又遇到了另外一个奇葩的问题: Keepalived 高可用组件的虚拟 IP 持续漂移,导致 MySQL 主从不断切 ...
- SpringBoot 开发案例之整合FastDFS分布式文件系统
1.pom依赖 <!--fastdfs--> <dependency> <groupId>com.github.tobato</groupId> < ...
- Linux系列之文本操作命令
前言 Linux 有八个常用的文本操作命令:cat.head.tail.nl.grep.sed.more.less.本文介绍它们的区别和简单用法. cat命令 显示文本的最基本命令. cat file ...
- Collection子接口:List接口
1. 存储的数据特点:存储序的.可重复的数据. 2. 常用方法:(记住)增:add(Object obj)删:remove(int index) / remove(Object obj)改:set(i ...
- Qt+ECharts开发笔记(二):Qt窗口动态调整大小,使ECharts跟随Qt窗口大小变换而变换大小
前言 上一篇将ECharts嵌入Qt中,在开始ECharts使用之前,还有一个很重要的功能,就是在窗口变换大小的时候,ECharts的图表尺寸也要跟随Qt窗口变换大小而变换大小. Demo演示 ...