P6773 [NOI2020] 命运
题面
给定一棵树,你需要给树上每条边确定染色。
同时给定 个限制,每条限制形如 到 路径上的边中存在一个黑色边,保证 是 的祖先。
问所有染色方案数模 。
数据范围: 。
题解
考虑一下链的情况,其实就是 CF1327F AND Segments 。
在那个题中,我们维护了 表示最后一个 在 这个位置时的方案数,然后随着当前 的增加,不断消除掉不合法的 ,同时维护总和来做到 转移。
但这题中是树的结构,我们无法做到消除不合法的状态,所以需要换一种做法。
考虑反过来,设 表示考虑以 为根的子树,其中未满足的限制中 深度最大的为 。
什么意思呢?我们把每个限制存在 上,对于相同的 ,我们只保留深度最大的(深度大的满足了,那么深度小的一定满足),记为 。
初始 ,其余为 ,考虑合并子树:
-
若 染成黑色,那么 子树内的限制一定都能满足,除了 ,其中 ,那么这些就在 这就不能满足了。
即 。
-
若 染成白色,那么相当于 子树内的是咋样还是咋样,即 。
现在就有 的DP了,即
重点是后面的优化过程,我们给每个节点 开一个线段树,用来维护 。
虽然这个DP式子看起来很复杂,但是我们发现只有 不和 相关,而这个就是一个前缀和,可以在线段树上直接查询。
而后面的每一个都是一个前缀和的形式,我们可以在线段树合并的时候记录一下两棵树的前缀和,同时更新。
具体可以参考一下代码:
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;
}
实现的时候需要注意一下更新前缀和、修改的顺序。
之后就没了,复杂度是 的。
启发
- 这道题告诉我们这种 树上+关于前缀和的 DP 是可以通过线段树合并做到 的。
本文作者:qwq_123
本文链接:https://www.cnblogs.com/qwq-123/p/16076929.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步