P6773 [NOI2020] 命运

题面

给定一棵树,你需要给树上每条边确定染色。

同时给定 m 个限制,每条限制形如 uv 路径上的边中存在一个黑色边,保证 uv 的祖先。

问所有染色方案数模 998244353

数据范围: n,m5×105

题解

考虑一下链的情况,其实就是 CF1327F AND Segments

在那个题中,我们维护了 fj 表示最后一个 1j 这个位置时的方案数,然后随着当前 i 的增加,不断消除掉不合法的 fj ,同时维护总和来做到 O(1) 转移。

但这题中是树的结构,我们无法做到消除不合法的状态,所以需要换一种做法。

考虑反过来,设 fi,j 表示考虑以 i 为根的子树,其中未满足的限制中 u 深度最大的为 j

什么意思呢?我们把每个限制存在 v 上,对于相同的 v ,我们只保留深度最大的(深度大的满足了,那么深度小的一定满足),记为 bv

初始 fx,bx=1,其余为 0,考虑合并子树:

  • (x,y) 染成黑色,那么 y 子树内的限制一定都能满足,除了 fy,j ,其中 j>dex ,那么这些就在 y 这就不能满足了。

    fx,ifx,ijdexfy,j

  • (x,y) 染成白色,那么相当于 y 子树内的是咋样还是咋样,即 fx,ifx,ijify,j+fy,ij<ifx,j

现在就有 n2 的DP了,即

fx,ifx,i(jdexfy,j+jify,j)+fy,ij<ifx,j

重点是后面的优化过程,我们给每个节点 x 开一个线段树,用来维护 fx,i

虽然这个DP式子看起来很复杂,但是我们发现只有 jdexfy,j 不和 i 相关,而这个就是一个前缀和,可以在线段树上直接查询。

而后面的每一个都是一个前缀和的形式,我们可以在线段树合并的时候记录一下两棵树的前缀和,同时更新。

具体可以参考一下代码:

ll su1,su2=query(root[y],de[x],0,N);//查询在y树上的前缀和。
int hb(int p1,int p2,int l,int r){//直接合并。
	if(!p1&&!p2)return 0;
	if(!p1){
		su2=(su2+tr[p2].su)%mod;
		chuli(p2,su1);return p2;//chuli 是处理这个区间乘上某个数。
	}
	if(!p2){
		su1=(su1+tr[p1].su)%mod;
		chuli(p1,su2);return p1;
	}
	if(l==r){//注意顺序。
		su2=(su2+tr[p2].su)%mod;chuli(p2,su1);
		su1=(su1+tr[p1].su)%mod;chuli(p1,su2);
		tr[p1].su=(tr[p1].su+tr[p2].su)%mod;
		return p1;
	}
	down(p1),down(p2);
	int mid=(l+r)>>1;
	tr[p1].ls=hb(tr[p1].ls,tr[p2].ls,l,mid);
	tr[p1].rs=hb(tr[p1].rs,tr[p2].rs,mid+1,r);
	up(p1);
	return p1;
}

实现的时候需要注意一下更新前缀和、修改的顺序。

之后就没了,复杂度是 O(nlogn) 的。

启发

  • 这道题告诉我们这种 树上+关于前缀和的 n2 DP 是可以通过线段树合并做到 O(nlogn) 的。

本文作者:qwq_123

本文链接:https://www.cnblogs.com/qwq-123/p/16076929.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   qwq_123  阅读(66)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起