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

本文介绍了如何利用树形动态规划(DP)解决一个图论问题:给定一棵树,节点有两种颜色,求度数为1的节点颜色相同的联通子图的数量。文章详细阐述了状态定义、状态转移方程,并提供了C++实现的参考代码。最后,文章提到原题数据可能存在问题,导致解题过程有些困惑。

原题链接

点击

题目大意

给定包含 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);
}

后话

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

### 暑期训练营的适合水平分析 暑期训练营是一项面向算法竞赛爱好者的系列比赛,主要目的是为选手提供一个练习和提升的机会[^1]。该训练营通常吸引了来自全国各地的编程爱好者以及准备参加 ACM-ICPC 或其他算法竞赛的学生参与。根据以往的经验,以下是对适合水平的详细分析: #### 1. **基础要求** 训练营中的题目难度跨度较大,从入门级到高难度均有覆盖。对于新手选手来说,如果具备一定的算法基础(如掌握基本的数据结构、排序算法、搜索算法等),可以尝试参与并从中学习[^2]。 #### 2. **中级选手** 中级水平的选手通常已经熟练掌握了常见的算法模板,例如动态规划、图论(最短路径、最小生成树等)、字符串匹配等。这类选手可以通过训练营中的中等难度题目进一步巩固知识,并挑战更高难度的问题以提升能力[^3]。 #### 3. **高级选手** 高级水平的选手通常是 ACM-ICPC 区域赛或更高级别比赛的参赛者。他们能够快速解决大部分常规问题,并专注于研究复杂算法和优化技巧。对于这些选手,训练营是一个检验自身实力、发现不足的好机会[^4]。 #### 4. **团队协作能力** 值得注意的是,训练营不仅考察个人能力,还强调团队合作的重要性。许题目需要名队员分工合作才能高效完成。因此,即使是高水平的个人选手,也需要通过训练营来磨练与队友的配合能力[^5]。 ```python # 示例代码:计算最短路径(Dijkstra算法) import heapq def dijkstra(graph, start): n = len(graph) dist = [float('inf')] * n dist[start] = 0 heap = [(0, start)] while heap: d, u = heapq.heappop(heap) if d > dist[u]: continue for v, w in graph[u]: if dist[u] + w < dist[v]: dist[v] = dist[u] + w heapq.heappush(heap, (dist[v], v)) return dist ``` 上述代码展示了图论中经典的 Dijkstra 算法实现,这是训练营中可能出现的基础知识点之一。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值