“蔚来杯“2022牛客暑期多校训练营5 D.Birds in the tree

原题链接

点击

题目大意

给定包含 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=sxsondps,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,1dpx,!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);
}

后话

原题有点迷,数据有点迷,我有点迷,,,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值