1 条题解

  • 0
    @ 2025-8-24 22:45:55

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar 2024sdhkdj
    **

    搬运于2025-08-24 22:45:55,当前版本为作者最后更新于2023-10-02 12:46:48,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    题面传送门

    我的 BLOG

    前置知识: 树形 DP

    题意描述

    给定两个数 mmnn,表示有 mmnn 个点的树。其中每棵树形态、结构相同,给定它们的 n1n-1 条边。需要你为每棵树上的所有结点赋值,令 a(i,j)a(i,j) 表示为第 ii 棵树上的第 jj 个点被赋的值。赋值规则如下:

    • 对于每棵树上任何一个结点,只能赋值为 0011

    • 对于每棵树上相同位置的节点,允许有其中一个结点赋值为 11

    • 对于每棵树上任意一条边,允许有其中一个端点赋值为 11

    要求出有多少种赋值方案数

    算法分析

    因为要求方案数,可以想到使用 DP。而题目要求是在树上操作,可以确定是树形 DP

    定义状态

    树形 DP 的状态比较套路,一般设两维(dp[i][j]dp[i][j])。第一维表示“以 ii 为根结点的字树”,第二维根据题意要求,表示 ii 号位置为 jj。意义什么呢?因为题目所求是赋值方案,于是可以断定:

    dp[i][j]dp[i][j] 表示,以 ii 为根结点的子树,第 ii 号位置为 jj 的方案数。

    但这个状态有一个小错误,假如其中一棵树 ii 号位置为 11,则其他树必须为 00,而不能全部为 11。因此需要分类讨论:

    1. j=0j=0 时:

      dp[i][j]dp[i][j] 表示,以 ii 为根结点的子树,第 ii 号位置为 00 的方案数。

    2. j=1j=1 时:

      dp[i][j]dp[i][j] 表示,以 ii 为根结点的子树,其中一棵树第 ii 号位置为 11,其他树为 00 的方案数。

    状态转移方程

    个人认为,找状态转移方程是此题的难点。一旦推出来了,剩下的就只是打板子了。

    根据我们定义的状态,需要分两种情况讨论:

    1. 当第 ii 位为 00 时:

      • 当它的儿子为 00 时:

        为以前的方案数乘以这个儿子的方案数:dp[i][0]=dp[i][0]×dp[son][0]dp[i][0] = dp[i][0] \times dp[son][0]

      • 当它的儿子为 11 时:

        对于每棵树的它的这个儿子的位置,都可以为 11,根据乘法原理,为以前的方案数乘以 mm 种为 11 的方案数:dp[i][0]=dp[i][0]×m×dp[son][1]dp[i][0] = dp[i][0] \times m \times dp[son][1]

    2. 当第 ii 位为 11 时:

      • 当它的儿子为 00 时:

        同样为以前的方案数乘以这个儿子的方案数:dp[i][1]=dp[i][1]×dp[son][0]dp[i][1] =dp[i][1] \times dp[son][0]

      • 当它的儿子为 11 时:

        对于每棵树的它的这个儿子的位置,除了它本身的儿子,其他的都可以为 11,根据乘法原理,为以前的方案数乘以 m1m-1 种为 11 的方案数:dp[i][1]=dp[i][1]×(m1)×dp[son][1]dp[i][1] =dp[i][1]\times (m-1) \times dp[son][1]

    合并得:

    1. $dp[i][0] =dp[i][0]\times (dp[son][0]+m \times dp[son][1])$。

    2. $dp[i][1] =dp[i][1] \times (dp[son][0]+(m-1) \times dp[son][1])$。

    答案

    根据我们定义的状态,答案为:以根结点为根的树,所有树根结点为 00 或任意一棵树根结点 11 的方案数之和。即:

    ans=dp[1][0]+m×dp[1][1]ans=dp[1][0]+m \times dp[1][1]

    初始状态

    根据状态转移方程,因为需要乘,如果每个 dp[i][j]dp[i][j] 设为 00,则最终答案只能为 00。因此将所有 dp[i][j]dp[i][j] 设为 11,即: i[1,n],j[0,1]\forall i \in [1,n],\forall j \in [0,1],有 a(i,j){1}a_{(i,j)}\in\{1\}

    注意事项

    • 因为是树,要建双向边,且在 dfs 时需要判父亲,不然会不断走重复的路,直至卡死。

    • 方案数可能很大,记得每转移一次就取模一次,并开 long long。

    • 一定记得先搜索再转移,不然它的儿子的方案数还未算出,转移是无效的。

    • 有表达不当出可以辅以代码注释理解。

    代码

    #include<bits/stdc++.h>
    #define mod 1000000007//模数
    using namespace std;
    const int N=1e6+10;
    int n,m;
    long long dp[N][2];//记得开long long
    vector<int> vec[N];
    void dfs(int cur,int fa){//因为是无向边,需要记录它的父亲以避免重复
    	for(int i=0;i<vec[cur].size();i++){
    		int to=vec[cur][i];
    		if(to==fa)//判断父亲
    			continue;
    		dfs(to,cur);//先搜索,再转移,不然dp[to][0/1]还没有算出来,转移无效
    		dp[cur][0]=(dp[cur][0]*(dp[to][0]+m*dp[to][1]%mod))%mod;
    		dp[cur][1]=(dp[cur][1]*(dp[to][0]+(m-1)*dp[to][1]%mod))%mod;//状态转移
    	}
    }
    signed main()
    {
    	ios::sync_with_stdio(false);
    	cin.tie(0);
    	cout.tie(0);
    	cin>>n>>m;
    	for(int i=1;i<n;i++){
    		int u,v;
    		cin>>u>>v;
    		vec[u].push_back(v);
    		vec[v].push_back(u);//记得存双向边
    	}
        for(int i=1;i<=n;i++)
            dp[i][0]=dp[i][1]=1;//初始状态
    	dfs(1,0);//从根结点深搜
    	cout<<(dp[1][0]+m*dp[1][1]%mod)%mod;//答案
    	return 0;
    }
    

    AC 记录

    总结

    此题的难点主要是找出状态转移方程,一旦找到了,其他问题也迎刃而解。如何找到呢?最重要的是理解题意和所求。在理清他们的关系后,经过模拟与假设,逐渐摸索出正解。当然,树形 DP 这一类题的细节也挺多的,我深有体会,有可能就是一个符号的差距,就是一片红和一片绿的差距。 因此,多注重细节,才能在考场上稳中求胜。

    插句题外话,CSP2023 即将到来,这有可能是我考前最后一次写题解了。在此预祝各位: CSP2023 RP+RP+\infty!!!

    • 1

    信息

    ID
    8437
    时间
    2000ms
    内存
    128MiB
    难度
    4
    标签
    递交数
    0
    已通过
    0
    上传者