Codeforces 1252B
Tag:树上DP,数学优化,图论,动态规划
题目分析
题目大意
给定一个无向树,求任意两个路径的终点都不向连的路径集个数。其中点数小于等于1e5
做法猜测
树上的问题,还是计数,复杂度要求在O(nlogn)之内,直接考虑树上DP。
动态规划设计
前置说明
记当前节点为
u
u
u,子节点为
v
i
v_{i}
vi
使用的编译指令为
g++ filename.cpp -o filename.exe -O2 -std=c++11
预处理头文件与常量定义为
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+17,M = 3e5+17;
存图方式为链式前向星,其中存图的代码可以参考下述
int n,tails,fr[N],to[M],nxt[M];
void add(int f,int t){
nxt[++tails] = fr[f];
fr[f] = tails;
to[tails] = t;
return;
}
遍历的方式为
void DFS(int u,int fa){
for(int zj=fr[u],v;zj;zj=nxt[zj]){
if((v=to[zj]) == fa) continue;
DFS(v,start);
}
return;
}
状态说明
记本节点为节点
u
u
u,其子节点为
v
v
v。
记每个节点存在的下列状态分别为
0
/
1
/
2
0/1/2
0/1/2
同时使用
F
[
N
]
[
3
]
F[N][3]
F[N][3]来记录每个状态
long long MOD = 1e9+7,F[N][4];
不要问我怎么出来的这个状态,Three Hours淦出来的
“自闭”状态
定义
u
u
u 节点已经连接向两个子节点,且其他节点均处在满足题意的状态为状态0。
“不满”状态
定义
u
u
u 节点已经连接了不足两个子节点;且如果
u
u
u 节点连向父节点后,以
u
u
u 为根的树满足题目中的要求的状态为状态1。
简单来说,就是如果
u
u
u 与其父节点连接时满足题意的状态。
“自信”状态
定义
u
u
u 节点在不考虑父节点的情况下,满足题意的状态为状态2。
状态分析
状态转移方程的确立
“自闭”状态
一个“自闭”状态的形成一定有两个“不满”状态影响,且其他子节点都在“自信”状态中。
于是我们得到了初步的计算式:
F
[
u
]
[
0
]
=
∑
v
1
,
v
2
∈
s
o
n
u
,
v
1
≠
v
2
(
F
[
v
1
]
[
1
]
∗
F
[
v
2
]
[
1
]
∗
∏
v
∈
s
o
n
u
,
v
≠
v
1
,
v
≠
v
2
F
[
v
]
[
2
]
)
F[u][0] = \sum_{v1,v2 \in son_{u} , v1 \ne v2} (F[v1][1] * F[v2][1] * \prod_{v \in son_{u},v \ne v1, v \ne v2}F[v][2])
F[u][0]=∑v1,v2∈sonu,v1=v2(F[v1][1]∗F[v2][1]∗∏v∈sonu,v=v1,v=v2F[v][2])
“不满”状态
一个“不满”状态可能的组成情况有下列两种:
- u u u 与一个 v v v 的“不满”状态相连接,其它节点均处在“自信”状态。
-
u
u
u 自成一体,所有
v
v
v 都处在“自闭”状态。
这个状态需要说明一下,如果有 v v v 不处在“自闭”状态,则即使 u u u 与其父节点相连的情况下,这个子树 v v v一定与 u u u 各处在一个路径的端点,而此两点相连,不满足题意。因此所有 v v v 均应当处在“自闭”状态。
F [ u ] [ 1 ] = ∑ v 1 ∈ s o n u ( F [ v 1 ] [ 1 ] ∗ ∏ v ∈ s o n u , v ≠ v 1 F [ v ] [ 2 ] ) + ∏ v ∈ s o n u F [ v ] [ 0 ] F[u][1] = \sum_{v1 \in son_{u}}(F[v1][1]* \prod_{v \in son_{u},v \ne v1}F[v][2]) + \prod_{v \in son_{u}}F[v][0] F[u][1]=∑v1∈sonu(F[v1][1]∗∏v∈sonu,v=v1F[v][2])+∏v∈sonuF[v][0]
“自信”状态
一个“自信”状态的构成,也可以分为两部分。
- 这个节点处在“自闭”状态。
- 这个节点处在所有 v v v 都“自闭”的状态。
- 这个节点处在与一个“不满”子节点相连,其他子节点均“自闭”的状态
F [ u ] [ 2 ] = F [ u ] [ 0 ] + ∏ v ∈ s o n u F [ v ] [ 0 ] + ∑ v 1 ∈ s o n u ( F [ v 1 ] [ 1 ] ∗ ∏ v ∈ s o n u , v ≠ v 1 F [ v ] [ 0 ] ) F[u][2] = F[u][0] + \prod_{v \in son_{u}}F[v][0] + \sum_{v1 \in son_{u}}(F[v1][1] *\prod_{v \in son_{u},v \ne v1}F[v][0]) F[u][2]=F[u][0]+∏v∈sonuF[v][0]+∑v1∈sonu(F[v1][1]∗∏v∈sonu,v=v1F[v][0])
初始状态转移的时空复杂度
时间复杂度
对于每一个节点,我们朴素计算每个单独的计算单元。
∏ v ∈ s o n u F [ v ] [ 2 ] \prod_{v \in son_{u}}F[v][2] ∏v∈sonuF[v][2]
∏ v ∈ s o n u F [ v ] [ 0 ] \prod_{v \in son_{u}}F[v][0] ∏v∈sonuF[v][0]
时间复杂度为 O ( s o n u ) O(son_{u}) O(sonu)
然后枚举 v 1 v1 v1,计算出
∑ v 1 ∈ s o n u ( F [ v 1 ] [ 1 ] ∗ ∏ v ∈ s o n u , v ≠ v 1 F [ v ] [ 0 ] ) \sum_{v1 \in son_{u}}(F[v1][1] *\prod_{v \in son_{u},v \ne v1}F[v][0]) ∑v1∈sonu(F[v1][1]∗∏v∈sonu,v=v1F[v][0])
∑ v 1 ∈ s o n u ( F [ v 1 ] [ 1 ] ∗ ∏ v ∈ s o n u , v ≠ v 1 F [ v ] [ 2 ] ) \sum_{v1 \in son_{u}}(F[v1][1]* \prod_{v \in son_{u},v \ne v1}F[v][2]) ∑v1∈sonu(F[v1][1]∗∏v∈sonu,v=v1F[v][2])
时间复杂度为 O ( s o n u ) O(son_{u}) O(sonu)
枚举 v 1 v1 v1 的同时,枚举 v 2 v2 v2,计算
∑ v 1 , v 2 ∈ s o n u , v 1 ≠ v 2 ( F [ v 1 ] [ 1 ] ∗ F [ v 2 ] [ 1 ] ∗ ∏ v ∈ s o n u , v ≠ v 1 , v ≠ v 2 F [ v ] [ 2 ] ) \sum_{v1,v2 \in son_{u} , v1 \ne v2} (F[v1][1] * F[v2][1] * \prod_{v \in son_{u},v \ne v1, v \ne v2}F[v][2]) ∑v1,v2∈sonu,v1=v2(F[v1][1]∗F[v2][1]∗∏v∈sonu,v=v1,v=v2F[v][2])
时间复杂度 O ( s o n u 2 ) O(son_{u}^{2}) O(sonu2)
总时间复杂度
O
(
n
2
)
O(n^{2})
O(n2)
显然超时。
空间复杂度
So easy,I won’t want to do this
动态规划方程的一种优化方式
基本思想
借鉴之前优化连乘算式的方法,我们考虑一个一个加入新的元素,并计算新加入元素的贡献。
举个例子
目标方程
∑ v 1 ∈ s o n u ( F [ v 1 ] [ 1 ] ∗ ∏ v ∈ s o n u , v ≠ v 1 F [ v ] [ 0 ] ) \sum_{v1 \in son_{u}}(F[v1][1] *\prod_{v \in son_{u},v \ne v1}F[v][0]) ∑v1∈sonu(F[v1][1]∗∏v∈sonu,v=v1F[v][0])
变量声明
v
0
v0
v0 为新遍历到的节点
s
o
n
1
u
son1_{u}
son1u 为在
v
0
v0
v0 前进入的子节点集合
优化分析
我们假设新加入了一个子节点 v 0 v0 v0
那么考虑 v 0 v0 v0 对在上式中 v 1 v1 v1 取其它节点的贡献
F [ v 0 ] [ 0 ] ∗ ∑ v 2 ∈ s o n 1 u ( F [ v 2 ] [ 1 ] ∗ ∏ v ∈ s o n 1 u , v ≠ v 2 F [ v ] [ 0 ] ) F[v0][0] * \sum_{v2 \in son1_{u}}(F[v2][1]*\prod_{v \in son1_{u},v \ne v2}F[v][0]) F[v0][0]∗∑v2∈son1u(F[v2][1]∗∏v∈son1u,v=v2F[v][0])
考虑 v 0 v0 v0 对上式中 v 1 v1 v1 取 v 0 v0 v0 时的贡献
F [ v 0 ] [ 1 ] ∗ ∏ v 2 ∈ s o n 1 u F [ v 2 ] [ 0 ] F[v0][1] *\prod_{v2 \in son1_{u}}F[v2][0] F[v0][1]∗∏v2∈son1uF[v2][0]
将以上两个式子相加即可得到加入一个节点 v 0 v0 v0 后的结果。
F [ v 0 ] [ 0 ] ∗ ∑ v 2 ∈ s o n 1 u ( F [ v 2 ] [ 1 ] ∗ ∏ v ∈ s o n 1 u , v ≠ v 2 F [ v ] [ 0 ] ) + F [ v 0 ] [ 1 ] ∗ ∏ v 2 ∈ s o n 1 u F [ v 2 ] [ 0 ] ) F[v0][0] * \sum_{v2 \in son1_{u}}(F[v2][1]*\prod_{v \in son1_{u},v \ne v2}F[v][0]) + F[v0][1] *\prod_{v2 \in son1_{u}}F[v2][0]) F[v0][0]∗∑v2∈son1u(F[v2][1]∗∏v∈son1u,v=v2F[v][0])+F[v0][1]∗∏v2∈son1uF[v2][0])
此时我们发现一个重要的事情,即上面
∑ v 2 ∈ s o n 1 u ( F [ v 2 ] [ 1 ] ∗ ∏ v ∈ s o n 1 u , v ≠ v 2 F [ v ] [ 0 ] ) \sum_{v2 \in son1_{u}}(F[v2][1]*\prod_{v \in son1_{u},v \ne v2}F[v][0]) ∑v2∈son1u(F[v2][1]∗∏v∈son1u,v=v2F[v][0])
不正是我们没有加入 v 0 v0 v0 时的结果吗?
所以我们不妨设计这样两个东西
s 1 = ∑ v 2 ∈ s o n 1 u ( F [ v 2 ] [ 1 ] ∗ ∏ v ∈ s o n 1 u , v ≠ v 2 F [ v ] [ 0 ] ) s1 = \sum_{v2 \in son1_{u}}(F[v2][1]*\prod_{v \in son1_{u},v \ne v2}F[v][0]) s1=∑v2∈son1u(F[v2][1]∗∏v∈son1u,v=v2F[v][0])
s 2 = ∏ v ∈ s o n 1 u F [ v ] [ 0 ] s2 = \prod_{v \in son1_{u}}F[v][0] s2=∏v∈son1uF[v][0]
代码实现
于是在代码中,我们就可以这样维护这两个变量
void DFS(int u,int fa){
long long s1=0,s2=1;//Notice: s2 = 1
for(int zj=fr[u],v;zj;zj=nxt[zj]){
if((v=to[zj])==fa) continue;
DFS(v,u);
s1 = (s1*F[v][0] + s2*F[v][1])%MOD;
s2 = (s2*F[v][0])%MOD;
}
}
时空复杂度
时间复杂度
由于每次遍历到新的子节点都做了常数次操作,因此我们
推广一下
简单改名
我们通过改改名字就能实现下面这个式子
∑ v 1 ∈ s o n u ( F [ v 1 ] [ 1 ] ∗ ∏ v ∈ s o n u , v ≠ v 1 F [ v ] [ 2 ] ) \sum_{v1 \in son_{u}}(F[v1][1] *\prod_{v \in son_{u},v \ne v1}F[v][2]) ∑v1∈sonu(F[v1][1]∗∏v∈sonu,v=v1F[v][2])
记住这个式子👆叫做
w
2
w2
w2
记住这个式子👆叫做
w
2
w2
w2
记住这个式子👆叫做
w
2
w2
w2
这么简单我就直接上代码了
void DFS(int u,int fa){
long long w2=0,w3=1,s1=0,s2=1;
for(int zj=fr[u],v;zj;zj=nxt[zj]){
if((v=to[zj])==fa) continue;
else DFS(v,u);
w2 = (w2*F[v][2] + w3*F[v][1])%MOD;
w3 = (w3*F[v][2])%MOD;
s1 = (s1*F[v][0] + s2*F[v][1])%MOD;
s2 = (s2*F[v][0])%MOD;
}
}
双变量枚举
做完上面的这些工作之后,我们就剩下一个式子没有处理了。
先把它给供起来,真的脑筋急转弯
∑ v 1 , v 2 ∈ s o n u , v 1 ≠ v 2 ( F [ v 1 ] [ 1 ] ∗ F [ v 2 ] [ 1 ] ∗ ∏ v ∈ s o n u , v ≠ v 1 , v ≠ v 2 F [ v ] [ 2 ] ) \sum_{v1,v2 \in son_{u} , v1 \ne v2} (F[v1][1] * F[v2][1] * \prod_{v \in son_{u},v \ne v1, v \ne v2}F[v][2]) ∑v1,v2∈sonu,v1=v2(F[v1][1]∗F[v2][1]∗∏v∈sonu,v=v1,v=v2F[v][2])
此处建议大家手动推一下这个式子的优化。
还是老规矩,考虑新加入节点对原先节点的影响。
符号说明
v
0
v0
v0 新加入节点
s
o
n
1
u
son1_{u}
son1u 在
v
0
v0
v0 之前加入的节点
开工动手
既然原始式子中取了两个变量,那我们不妨假设
v
1
<
v
2
v1 < v2
v1<v2
如果原始式子中的
v
2
v2
v2 取
v
0
v0
v0 的话,我们考虑其对答案更新的贡献
F [ v 0 ] [ 1 ] ∗ ∑ v 3 ∈ s o n 1 u ( F [ v 3 ] [ 1 ] ∗ ∏ v ∈ s o n 1 u , v ≠ v 3 F [ v ] [ 2 ] ) F[v0][1] * \sum_{v3 \in son1_{u}} (F[v3][1] * \prod_{v \in son1_{u},v \ne v3}F[v][2]) F[v0][1]∗∑v3∈son1u(F[v3][1]∗∏v∈son1u,v=v3F[v][2])
如果原始式子中 v 1 v1 v1 与 v 2 v2 v2 均不取 v 0 v0 v0 的话,考虑其对答案更新的贡献
F [ v 0 ] [ 2 ] ∗ ∑ v 1 , v 2 ∈ s o n 1 u , v 1 ≠ v 2 ( F [ v 1 ] [ 1 ] ∗ F [ v 2 ] [ 1 ] ∗ ∏ v ∈ s o n 1 u , v ≠ v 1 , v ≠ v 2 F [ v ] [ 2 ] ) F[v0][2] * \sum_{v1,v2 \in son1_{u} , v1 \ne v2} (F[v1][1] * F[v2][1] * \prod_{v \in son1_{u},v \ne v1, v \ne v2}F[v][2]) F[v0][2]∗∑v1,v2∈son1u,v1=v2(F[v1][1]∗F[v2][1]∗∏v∈son1u,v=v1,v=v2F[v][2])
奇妙发现1
我们看下面这个式子跟 w 2 w2 w2 有啥关系吗?
F [ v 0 ] [ 1 ] ∗ ∑ v 3 ∈ s o n 1 u ( F [ v 3 ] [ 1 ] ∗ ∏ v ∈ s o n 1 u , v ≠ v 3 F [ v ] [ 2 ] ) F[v0][1] * \sum_{v3 \in son1_{u}} (F[v3][1] * \prod_{v \in son1_{u},v \ne v3}F[v][2]) F[v0][1]∗∑v3∈son1u(F[v3][1]∗∏v∈son1u,v=v3F[v][2])
嘻嘻嘻,相信你也发现了,上面这个式子等价于下面的这个
F [ v 0 ] [ 1 ] ∗ ∑ v 3 ∈ s o n 1 u ( F [ v 3 ] [ 1 ] ∗ ∏ v ∈ s o n 1 u , v ≠ v 3 F [ v ] [ 2 ] ) ⟺ F [ v 0 ] [ 1 ] ∗ w 2 F[v0][1] * \sum_{v3 \in son1_{u}} (F[v3][1] * \prod_{v \in son1_{u},v \ne v3}F[v][2]) \iff F[v0][1]*w2 F[v0][1]∗∑v3∈son1u(F[v3][1]∗∏v∈son1u,v=v3F[v][2])⟺F[v0][1]∗w2
奇妙发现2
我们看下面这个式子是什么
∑ v 1 , v 2 ∈ s o n 1 u , v 1 ≠ v 2 ( F [ v 1 ] [ 1 ] ∗ F [ v 2 ] [ 1 ] ∗ ∏ v ∈ s o n 1 u , v ≠ v 1 , v ≠ v 2 F [ v ] [ 2 ] ) \sum_{v1,v2 \in son1_{u} , v1 \ne v2} (F[v1][1] * F[v2][1] * \prod_{v \in son1_{u},v \ne v1, v \ne v2}F[v][2]) ∑v1,v2∈son1u,v1=v2(F[v1][1]∗F[v2][1]∗∏v∈son1u,v=v1,v=v2F[v][2])
这不正是在 v 0 v0 v0 之前的所有点的结果吗?
我们再拿个 w 1 w1 w1 来维护这个结果,于是可以得到下段程序
void DFS(int u,int fa){
long long w1=0,w2=0,w3=1,s1=0,s2=1;
for(int zj=fr[u],v;zj;zj=nxt[zj]){
if((v=to[zj])==fa) continue;
else DFS(v,u);
w1 = (w1*F[v][2] + w2*F[v][1])%MOD;
w2 = (w2*F[v][2] + w3*F[v][1])%MOD;
w3 = (w3*F[v][2])%MOD;
s1 = (s1*F[v][0] + s2*F[v][1])%MOD;
s2 = (s2*F[v][0])%MOD;
}
}
合并计算结果
由上面式子
F [ u ] [ 0 ] = ∑ v 1 , v 2 ∈ s o n u , v 1 ≠ v 2 ( F [ v 1 ] [ 1 ] ∗ F [ v 2 ] [ 1 ] ∗ ∏ v ∈ s o n u , v ≠ v 1 , v ≠ v 2 F [ v ] [ 2 ] ) F[u][0] = \sum_{v1,v2 \in son_{u} , v1 \ne v2} (F[v1][1] * F[v2][1] * \prod_{v \in son_{u},v \ne v1, v \ne v2}F[v][2]) F[u][0]=∑v1,v2∈sonu,v1=v2(F[v1][1]∗F[v2][1]∗∏v∈sonu,v=v1,v=v2F[v][2])
F [ u ] [ 1 ] = ∑ v 1 ∈ s o n u ( F [ v 1 ] [ 1 ] ∗ ∏ v ∈ s o n u , v ≠ v 1 F [ v ] [ 2 ] ) + ∏ v ∈ s o n u F [ v ] [ 0 ] F[u][1] = \sum_{v1 \in son_{u}}(F[v1][1]* \prod_{v \in son_{u},v \ne v1}F[v][2]) + \prod_{v \in son_{u}}F[v][0] F[u][1]=∑v1∈sonu(F[v1][1]∗∏v∈sonu,v=v1F[v][2])+∏v∈sonuF[v][0]
F [ u ] [ 2 ] = F [ u ] [ 0 ] + ∏ v ∈ s o n u F [ v ] [ 0 ] + ∑ v 1 ∈ s o n u ( F [ v 1 ] [ 1 ] ∗ ∏ v ∈ s o n u , v ≠ v 1 F [ v ] [ 0 ] ) F[u][2] = F[u][0] + \prod_{v \in son_{u}}F[v][0] + \sum_{v1 \in son_{u}}(F[v1][1] *\prod_{v \in son_{u},v \ne v1}F[v][0]) F[u][2]=F[u][0]+∏v∈sonuF[v][0]+∑v1∈sonu(F[v1][1]∗∏v∈sonu,v=v1F[v][0])
可以得到下面这段代码
void DFS(int u,int fa){
long long w1=0,w2=0,w3=1,s1=0,s2=1;
for(int zj=fr[u],v;zj;zj=nxt[zj]){
if((v=to[zj])==fa) continue;
else DFS(v,u);
w1 = (w1*F[v][2] + w2*F[v][1])%MOD;
w2 = (w2*F[v][2] + w3*F[v][1])%MOD;
w3 = (w3*F[v][2])%MOD;
s1 = (s1*F[v][0] + s2*F[v][1])%MOD;
s2 = (s2*F[v][0])%MOD;
}
F[u][0] = w1;
F[u][1] = (w2 + s2)%MOD;;
F[u][2] = (F[u][0] + s1 + s2)%MOD;
}
完整代码
主函数那么简单,我就不用说明了吧
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+17,M = 3e5+17;
long long MOD = 1e9+7,F[N][4];
int n,tails,fr[N],to[M],nxt[M];
void add(int f,int t){
nxt[++tails] = fr[f];
fr[f] = tails;
to[tails] = t;
}
void DFS(int u,int fa){
long long w1=0,w2=0,w3=1,s1=0,s2=1;
for(int zj=fr[u],v;zj;zj=nxt[zj]){
if((v=to[zj])==fa) continue;
else DFS(v,u);
w1 = (w1*F[v][2] + w2*F[v][1])%MOD;
w2 = (w2*F[v][2] + w3*F[v][1])%MOD;
w3 = (w3*F[v][2])%MOD;
s1 = (s1*F[v][0] + s2*F[v][1])%MOD;
s2 = (s2*F[v][0])%MOD;
}
F[u][0] = w1;
F[u][1] = (w2 + s2)%MOD;;
F[u][2] = (F[u][0] + s1 + s2)%MOD;
}
int main(){
scanf("%d",&n);
for(int i=1,p1,p2;i<n;++i){
scanf("%d %d",&p1,&p2);
add(p1,p2),add(p2,p1);
}
DFS(1,0),printf("%I64d",F[1][2]);
return 0;
}