Codeforces Round 926 (Div. 2) D. Sasha and a Walk in the City【树形dp】

原题链接:https://codeforces.com/problemset/problem/1929/D

题目描述

Sasha 想和他的女友在城市中散步。该城市由 n 个路口组成,编号为 1 到 n 。它们中的某些被道路连接,从任何一个路口出发,都可以恰好通向任何其他路口,换句话说,这些路口和它们之间的道路组成了一棵树。

其中有一些路口被认为很危险。由于在城市中独自走路是不安全的,所以Sasha 不想在散步时经过三个或更多的危险路口。

Sasha 认为,一组路口如果满足以下条件则称它为好的:

  • 如果在城市中只有这组路口中的路口是危险的,那么城市中的任何简单路径都包含不超过两个危险路口。

然而,Sasha 并不知道哪些路口是危险的,因此他对城市中不同好的路口组合的数量感兴趣。由于这个数量可能很大,输出其模数为 998244353 的值。

简单路径是指最多经过每个路口一次的路径。

输入格式

每个测试包含多个测试用例。第一行包含一个整数 t (1≤t≤10^4) —— 测试用例的数量。接下来是对于每个测试用例的描述:

每个测试用例的第一行包含一个整数 n (2≤n≤3×10^5) —— 城市中的路口数。

接下来 n−1 行描述了这些道路。其中第 i 行包含两个整数ui​ 和 vi​ (1≤ui​,vi​≤n,ui​!=vi​) 代表一条连接 ui​ 和 vi​ 的道路。

保证这些道路形成一棵树。

保证所有测试用例中 n 的总和不超过 3×10^5。

输出格式

对于每个测试用例,输出一个整数,即合法路口集合的数量对 998244353 取模的结果。

提示

在第一个测试用例中,有 2^3−1=7 个集合是可以选的,除了集合 {1,2,3} 以外,因为如果在城市中只有 {1,2,3} 中的路口是危险的,那么路口 1,2,3 对应的道路构成的简单路径 1−2−3 包含了 3 个危险路口。因此,一共有 7−1=6 个合法路口集合。

在第二个测试用例中,有 2^4−1=15 个集合是可以选的,但是其中 {1,2,3,4},{1,2,3},{1,3,4},{2,3,4} 不是合法的集合。因此一共有 15−4=11 个合法路口集合。城市分布如下图所示:

输入输出样例
输入 
4
3
1 3
3 2
4
3 4
2 3
3 1
5
1 2
3 4
5 1
2 3
4
1 2
2 3
3 4
输出 
7
12
16
11

解题思路:

最开始没有怎么理解题目意思,下面我先转换一下题意,这个题目实际上就是让我们对这棵树进行染色,使得不存在某一条路径上有三个或者三个以上的染色的点,问满足这个要求的染色的方案数有多少,这种方案数非常大需要取模的问题第一时间就想到了树形dp,关键是如何定义状态方便计算,定义f(i)表示以i为根节点的子树中不存在任意俩个点有祖先-后代关系并且这个子树至少会选择一个点的方案数,通过设定至少选择一个结点这个限制可以保证计算不重不漏,那么我们可以得出状态转移方程f(u)=(f(v1)+1)*(f(v2+1)***(f(vk)+1)  (v1,v2,...,vk表示u的所有儿子结点),为了保证不重不漏,当u只有一个子树选择点或者所有子树都不选择点时,必须选上结点u,只有仅选择u的情况向上传,其他的都是不选择u向上传,那么每个子树中不能存在有祖先-后代关系的俩个点并且至少选择一个点的方案书就是f(v),然后还需要加上v子树一个点也不选的方案,所以每一种情况还要加1。最终答案就是f(1)+f(2)+...f(n)+1,那么最后为什么还要加1呢,这个加1的树中所有点都不染色的情况。

时间复杂度:O(n)。

空间复杂度:O(n)。

cpp代码如下:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long LL;
const int N=3e5+10,M=N*2,mod=998244353;

int T,n;
int h[N],e[M],ne[M],idx;
int f[N];
int ans;

void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void dfs(int u,int fa)
{
    f[u]=1;
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(j==fa)continue;
        dfs(j,u);
        f[u]=(1ll*f[u]*(f[j]+1))%mod;
    }
    ans=(ans+f[u])%mod;
}
void solve()
{
    cin>>n;
    for(int i=0;i<=n+5;i++)h[i]=-1,f[i]=0;
    idx=0,ans=0;
    for(int i=0;i<n-1;i++)
    {
        int u,v;
        cin>>u>>v;
        add(u,v),add(v,u);
    }
    dfs(1,-1);
    cout<<(ans+1)%mod<<'\n';
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>T;
    while(T--)
    {
        solve();
    }
    return 0;
}
  • 14
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值