原题链接
题目大意
给定包含
n
n
n 个节点的树,每个节点具有颜色
0
0
0 或者颜色
1
1
1 。求其有多少联通子图,满足度数为
1
1
1 的节点颜色相同。
答案很大,对
1
0
9
+
7
10^9+7
109+7 取模。
题解
这是一个计数问题,肉眼可见的,我们无法通过暴力枚举情况解决,也无法通过一定的数学技巧 我不会 来解决问题,所以我们可以自然地想到解决方法,
D
P
DP
DP 。更加的,由题目得,这是一棵树,所以我们可以使用树形DP来解决这个问题。
设状态
一般的,我们设树形DP状态为某个节点下子树的状态,考虑这题有颜色的限制,所以我们设的状态为 d p x , c o l dp_{x,col} dpx,col 表示该子树下,该颜色下,有多少个满足条件的询问,由于经过该点的目标子图可以合并成一个状态来解决,所以我们可以令该状态下必定经过 x x x 节点,使得计算方便。
状态转移
如果不考虑其他限制,即一个子图颜色都相同,添加一个根所添加的,其余节点的可能 ++ ,则容易推得
d
p
x
,
c
o
l
=
∏
s
∈
x
s
o
n
d
p
s
,
c
o
l
+
1
dp_{x,col}=\prod_{s \in x_{son}} dp_{s,col}+1
dpx,col=∏s∈xsondps,col+1 ,可以考虑得,在该子图的
a
n
s
=
d
p
x
,
0
+
d
p
x
,
1
ans=dp_{x,0}+dp_{x,1}
ans=dpx,0+dpx,1 ,
而无法成立的状态,我们需要删去子图中首位不向符合的可能,即
∑
d
p
x
,
!
c
o
l
\sum{dp_{x,!col}}
∑dpx,!col ,
综上,对于每一个子图,我们需要实现的状态转移为
a
n
s
+
=
d
p
x
,
0
+
d
p
x
,
1
−
∑
d
p
x
,
!
c
o
l
ans+=dp_{x,0}+dp_{x,1}-\sum{dp_{x,!col}}
ans+=dpx,0+dpx,1−∑dpx,!col 。
另外的,根节点只有一种可能,所以计算后的
d
p
x
,
!
c
o
l
dp_{x,!col}
dpx,!col 的可能需要
−
1
-1
−1 。
实现
在一棵无根树中选择一个节点为根,跑 d f s dfs dfs 和 树形DP,经过状态转移方程,将结果累加。
参考代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=3e5+5;
const int mod=1e9+7;
vector<int> v[N];
int n;
char c[N];
int b[N];
int dp[N][2];
ll ans;
void dfs(int x)
{
b[x]=1;
dp[x][0]=dp[x][1]=1;
ll sum=0;
for(int i=0;i<v[x].size();i++) //转移哒
{
int son=v[x][i];
if(b[son])
continue;
dfs(son);
sum+=dp[son][!c[x]];
dp[x][c[x]]=1ll*dp[x][c[x]]*(dp[son][c[x]]+1)%mod;
dp[x][!c[x]]=1ll*dp[x][!c[x]]*(dp[son][!c[x]]+1)%mod;
}
sum%=mod;
dp[x][!c[x]]--;
ans=((1ll*ans+dp[x][0]+dp[x][1]-sum)%mod+mod)%mod; //结果可能为负数,取模时需要注意
}
int main()
{
scanf("%d",&n);
cin>>c+1;
for(int i=1;i<=n;i++) //字符串转换成数字,方便操作
c[i]=c[i]-'0';
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
v[x].push_back(y);
v[y].push_back(x);
}
dfs(1);
printf("%lld",ans);
}
后话
原题有点迷,数据有点迷,我有点迷,,,