AT4543 Subtrees

题目链接

洛谷:AT4543

AT:AT4543/DP_V


题意简述

给定一棵树,用黑白两色染色。

对于每一个点,求强制给该点染上黑色时,整棵树上的黑点构成一个连通块的染色方案数。

答案取模,模数输入给出。


题解

题目要求我们求出对每一个点强制染黑的情形的答案,故考虑采用换根 DP。

先计算钦定 1 1 1 号点为根并染黑的方案数。

f u f_u fu 表示将 u u u 号点染黑,且其子树内黑点构成连通块的方案数。易见,对于其每一个子节点 v v v,都有染黑和不染黑两种选择:染黑则方案数为 f v f_v fv;不染黑则 v v v 的整棵子树都只能为白,方案数为 1 1 1。故

f u = ∏ v ∈ s o n u ( f v + 1 ) f_u = \prod_{v \in \mathrm{son}_u} (f_v + 1) fu=vsonu(fv+1)

u u u 为叶子时,显然 f u = 1 f_u = 1 fu=1,与初始化要求相同。

现在考虑换根。不难理解,换根时消去 u u u f a u \mathrm{fa}_u fau 的贡献,再将 f a u \mathrm{fa}_u fau 的贡献乘到 u u u 上。

  • 消去 u u u f a u \mathrm{fa}_u fau 的贡献:

    f f a u ′ = f f a u f u + 1 f^\prime_{\mathrm{fa}_u} = \frac{f_{\mathrm{fa}_u}}{f_u + 1} ffau=fu+1ffau

  • f a u \mathrm{fa}_u fau 的贡献乘到 u u u 上:

    f u ′ = f u × ( f f a u ′ + 1 ) f^\prime_u = f_u \times (f^\prime_{\mathrm{fa}_u} + 1) fu=fu×(ffau+1)

不过出现了一个小问题:模数不保证是质数,所以不能用直接乘逆元的方式来消去贡献。

这里,对于每一个点 u u u,处理出 ( f v ∈ s o n u + 1 ) (f_{v \in \mathrm{son}_u} + 1) (fvsonu+1) 的前缀积和后缀积,即可解决求出消去一个子树贡献后的答案的问题。


代码

#include<bits/stdc++.h>
#define eb emplace_back

using namespace std;

namespace acah
{
	using ll = long long;
	
	constexpr int maxn = 1e5 + 7;
	
	int N, p;
	vector<int> T[maxn], sn[maxn];
	ll f[maxn], ans[maxn];
	vector<ll> pf[maxn], sf[maxn];
	
	void dp(int u, int fa)
	{
		f[u] = 1;
		
		for(int v : T[u]) {
			if(v == fa) continue;
			sn[u].eb(v);
			dp(v, u);
			(f[u] *= (f[v] + 1)) %= p;
		}
		
		pf[u].eb(1); // pf[u][0] = 1
		for(int i = 0, bd = sn[u].size(); i < bd; i++)
			pf[u].eb(pf[u][i] * (f[sn[u][i]] + 1) % p);
		
		sf[u].eb(1);
		for(int i = sn[u].size() - 1, c = 0; i >= 0; i--, c++)
			sf[u].eb(sf[u][c] * (f[sn[u][i]] + 1) % p);
		reverse(sf[u].begin(), sf[u].end());
	}
	
	void solve(int u, ll fv)
	{
		ans[u] = f[u] * (fv + 1) % p;
		
		for(int i = 0, bd = sn[u].size(); i < bd; i++) {
			int v = sn[u][i];
			ll l = pf[u][i], r = sf[u][i + 1];
			solve(v, l * r % p * (fv + 1) % p);
		}
	}
	
	int work()
	{
		scanf("%d%d", &N, &p);
		for(int i = 1, u, v; i < N; i++) {
			scanf("%d%d", &u, &v);
			T[u].eb(v), T[v].eb(u);
		}
		
		dp(1, 0), solve(1, 0);
		
		for(int i = 1; i <= N; i++)
			printf("%lld\n", ans[i]);
		
		return 0;
	}
}

int main() {return acah::work();}

这里,因为要使用儿子的编号,所以用基于范围的 for 循环并不如枚举下标简便。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值