Codeforces 1276D

Codeforces 题面传送门 & 洛谷题面传送门

繁琐的简单树形 dp(大雾),要是现场肯定弃了去做 F 题

做了我一中午,写篇题解纪念下。

提供一种不太一样的思路。

首先碰到这样的题肯定是没法用正常的组合计数方法求解,因此我们考虑树形 (dp)​。显然,如果对于每条边 ((u,v))​ 而言,我们确定扫描到这条边时是删除 (u)​ 上的标记、还是删除 (v)​ 上的标记,还是 (u,v)​ 上已经有一个标记消失了(即啥也不删),并且这种钦定方式合法(即,当扫描到某条边 (e) 时,不会出现你钦定要删除 (e) 某个端点上的标记,却已经有某个端点上的标记消失了;或者你钦定啥也不删,却有 (e) 的两个端点上的标记都没移走),那么我们就能唯一确定最后得到的序列。

考虑从这个性质入手解决问题,一个很自然的想法是设 (dp_x) 表示有多少种合法的钦定 (x) 子树中的边的方式,不过显然这东西不太好转移,因此考虑新加一维常数维记录一些信息。注意到对于一个点 (x) 而言,((x,fa_x)) 的决策也会影响到 (x) 与其儿子相连的边的决策,因此考虑将 ((x,fa_x)) 这条边也纳入 DP 状态,即 (dp_x) 的定义变为,有多少种合法的钦定 (x) 子树中的边及 (x) 与其父亲的边的方式。我们还能发现,当我们删除到边 ((x,fa_x)) 时,有以下五种可能:

  • (fa_x) 上的标记还在,但 (x)​ 上的标记已经没了
  • (x)​​ 上的标记还在,但 (fa_x)​​ 上的标记已经没了
  • (x,fa_x) 上的标记都没了
  • (x,fa_x) 上的标记都在,并且我们移走了 (x)​ 上的标记
  • (x,fa_x) 上的标记都在,并且我们移走了 (fa_x) 上的标记

在下文中分别称这五种情况为 Condition (0sim)​ Condition (4)​。考虑增加一维 (dp_{x,j})​ 我们在强制扫描到 (x)​ 与其父亲的边时情况为 Condition (j)​ 的情况下,有多少种合法的钦定 (x)​ 子树内边的方案数。考虑对五种情况分别转移:

  • Condition (0):由于 (x) 上的标记已经没了,因此我们肯定会在删除某一条编号小于 ((x,fa_x)) 且与 (x) 相连的边时,删除了 (x) 上的标记,因此我们考虑设 (f_y) 为有多少种钦定方案,满足我们恰好在删除 ((x,y)) 这条边时删除了 (x) 的标记,那么有 (dp_{x,0}=sumlimits_{ ext{id}(x,fa_x)> ext{id}(x,y)}f_y),其中 ( ext{id}(x,y)) 表示 ((x,y)) 边的编号,(f_y) 的求法将在下文中讲解。

  • Condition (1):首先对于与 (x) 相连,且满足 ( ext{id}(x,y)< ext{id}(x,fa_x)) 的边,我们肯定不能删除 (x) 上的标记,并且我们删除 ((x,y)) 边也就是 ((y,fa_y)) 时候,(x) 上的标记肯定还是在的,因此只有两种可能 (dp_{y,0})(dp_{y,3}),乘法原理乘起来即可,( ext{id}(x,y)> ext{id}(x,fa_x)) 的边,有两种选择,要么不存在某条 ((x,y)) 删除了 (x) 上的标记,方案数自然也是 (dp_{y,0}+dp_{y,3}),要么存在,方案数就是 (sumlimits_{ ext{id}(x,fa_x)< ext{id}(x,y)}f_y),两部分加起来可得

    [dp_{y,1}=prodlimits_{(x,y)in E}(dp_{y,0}+dp_{y,3})+sumlimits_{ ext{id}(x,fa_x)< ext{id}(x,y)}f_y ]

  • Condition (2):聪明的读者应该能发现,对于 Condition (0) 和 Condition (2) 两种情况,它们对于 ( ext{id}(x,y)< ext{id}(x,fa_x)) 的限制相同,对于 ( ext{id}(x,y)> ext{id}(x,fa_x)) 的限制也相同,因此 (dp_{x,2}=dp_{x,0})

  • Condition (3)​:还是分为 ( ext{id}(x,y)< ext{id}(x,fa_x))​ 和 ( ext{id}(x,y)> ext{id}(x,fa_x))​ 两类边,( ext{id}(x,y)< ext{id}(x,fa_x))​ 的边的方案数与 Condition (1)​ 那一部分相同,为 (dp_{y,0}+dp_{y,3})​,( ext{id}(x,y)> ext{id}(x,fa_x))​ 类比 Condition (1)​ 推理一下可知,删到 ((x,y))​ 时只可能是 Condition (1,2),因为不管怎样删到 ((x,y))(x) 上的标记已经没了,因此贡献为 (dp_{y,1}+dp_{y,2})​,由乘法原理可知

    [dp_{y,3}=prodlimits_{ ext{id}(x,fa_x)> ext{id}(x,y)}(dp_{y,0}+dp_{y,3})·prodlimits_{ ext{id}(x,fa_x)< ext{id}(x,y)}(dp_{y,1}+dp_{y,2}) ]

  • Condition (4):同理可得 (dp_{x,4}=dp_{x,1}),因为它们关于 ( ext{id}(x,y)< ext{id}(x,fa_x))( ext{id}(x,y)> ext{id}(x,fa_x)) 两种情况的限制均对应相同。

接下来考虑怎么求 (f_y)​​,显然 ((x,y))​​ 的贡献为 (dp_{y,4})​​,对于我们也可以将所有与 (x)​​ 相连且不同于 ((x,fa_x),(x,y))​​ 的边,我们也可以将它们分为两类:( ext{id}(x,z)< ext{id}(x,y))​​ 和 ( ext{id}(x,z)> ext{id}(x,y))​​,仿照 Condition (3)​​ 的分析过程可知,第一部分的贡献为 (dp_{z,0}+dp_{z,3})​,第二部分贡献为 (dp_{z,1}+dp_{z,2})​,乘法原理乘起来可得:

[f_y=dp_{y,4}·prodlimits_{ ext{id}(x,z)< ext{id}(x,y)}(dp_{z,0}+dp_{z,3})·prodlimits_{ ext{id}(x,z)> ext{id}(x,y)}(dp_{z,1}+dp_{z,2}) ]

预处理前后缀积即可 (mathcal O(1)) 计算,总复杂度线性。

似乎我的这个 solution 把 condition (0,2)​ 和 condition (1,4)​ 合并之后就是 CF 官方题解给的写法?不过我这个 DP 状态至少能体现我自己的思考过程吧(

const int MAXN=2e5;
const int MOD=998244353;
int n,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=1,bot[MAXN+5];
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int dp[MAXN+5][5],mul[MAXN+5];
void dfs(int x,int f){
	dp[x][1]=dp[x][3]=1;
	for(int e=hd[x];e;e=nxt[e]){
		int y=to[e];if(y==f) continue;bot[y]=e>>1;dfs(y,x);
		if(bot[y]<bot[x]) dp[x][3]=1ll*dp[x][3]*(dp[y][0]+dp[y][3])%MOD;
		else dp[x][3]=1ll*dp[x][3]*(dp[y][1]+dp[y][2])%MOD;
		dp[x][1]=1ll*dp[x][1]*(dp[y][0]+dp[y][3])%MOD;
	} vector<int> vec,pre_mul,suf_mul;
	for(int e=hd[x];e;e=nxt[e]){int y=to[e];if(y==f) continue;vec.pb(y);}
	reverse(vec.begin(),vec.end());pre_mul.resize(vec.size());suf_mul.resize(vec.size());
	for(int i=0;i<vec.size();i++){
		int y=vec[i];
		pre_mul[i]=1ll*((!i)?1:pre_mul[i-1])*(dp[y][0]+dp[y][3])%MOD;
	} for(int i=(int)(vec.size())-1;~i;i--){
		int y=vec[i];
		suf_mul[i]=1ll*((i+1==vec.size())?1:suf_mul[i+1])*(dp[y][1]+dp[y][2])%MOD;
	} for(int i=0;i<vec.size();i++){
		int y=vec[i];
		int mul=dp[y][4];if(i) mul=1ll*mul*pre_mul[i-1]%MOD;
		if(i+1<vec.size()) mul=1ll*mul*suf_mul[i+1]%MOD;
		if(bot[y]<bot[x]) dp[x][0]=(dp[x][0]+mul)%MOD;
		else dp[x][1]=(dp[x][1]+mul)%MOD;
	} dp[x][4]=dp[x][1];dp[x][2]=dp[x][0];
}
int main(){
	scanf("%d",&n);bot[1]=n;
	for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
	dfs(1,0);printf("%d
",(dp[1][1]+dp[1][2])%MOD);
	return 0;
}
原文地址:https://www.cnblogs.com/ET2006/p/Codeforces-1276D.html