题解 [NOI2020] 命运
题意
给定一棵 个节点的有根树和 条祖先到后代的链。问有多少种把边权设置为 或 的方案使得每条链上至少有一条边是 。
答案对 取模。
题解
我们将链的下端称为限制的起点。容易发现,对于同一个起点,终点越深,限制越强,于是不妨只考虑起点。
设 表示限制的起点在 的子树之内,不满足条件的限制最深深度为 的方案数。最终显然有 为答案。
考虑树形 DP,不断合并子树来求解 。首先遍历 为起点的限制,求出最深的深度 ,则初始子树中只有 一个点,。
考虑合并子树 ,根据 这条边是 还是 转移:
考虑第二个和式的上界是 ,因为 会出现两次。
记前缀和 ,
考虑线段树合并转移。这里讲一下转移过程: 在整个合并过程中都是常量,可以提前查询得到。合并到 时维护 和 ,因为我们是从左到右合并,于是这是容易维护的。
如果 的树上都有 的节点,直接递归合并。到叶子了就按照上面的式子直接转移。重点讲一下 有一个为空的情况:
- 为空,那么 ,于是上面式子里面 的项全是 ,且区间内的 。直接给 乘上 即可。
- 为空,那么 ,于是上面式子里面 的项全是 ,且区间内的 。直接给 乘上 即可。
# include <bits/stdc++.h>
const int N=500010,mod=998244353;
int n,m;
int dep[N],rt[N];
std::vector <int> G[N];
std::vector <int> lim[N];
struct Node{
int sum,lc,rc,tag;
Node(){
tag=1;
return;
}
}tr[N*30];
int cnt;
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-') f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
inline void add(int &x,int v){
x+=v;
if(x>=mod) x-=mod;
return;
}
inline int adc(int a,int b){
return (a+b<mod)?(a+b):(a+b-mod);
}
inline int mul(int a,int b){
return 1ll*a*b%mod;
}
inline int& lc(int x){
return tr[x].lc;
}
inline int& rc(int x){
return tr[x].rc;
}
inline void pushup(int x){
tr[x].sum=adc(tr[lc(x)].sum,tr[rc(x)].sum);
return;
}
inline void mule(int x,int v){
if(!x) return;
tr[x].sum=mul(tr[x].sum,v);
tr[x].tag=mul(tr[x].tag,v);
return;
}
inline void pushdown(int x){
if(tr[x].tag!=1)
mule(lc(x),tr[x].tag),mule(rc(x),tr[x].tag),tr[x].tag=1;
return;
}
void change(int &k,int l,int r,int x,int v){
if(!k) k=++cnt;
// printf("qwq = %d\n",tr[x].tag);
if(l==r) return tr[k].sum=v,void();
int mid=(l+r)>>1;
if(x<=mid) change(lc(k),l,mid,x,v);
else change(rc(k),mid+1,r,x,v);
pushup(k);
return;
}
void merge(int &k,int x,int y,int l,int r,int &gu,int &gv){ // gu[i-1] gv[i-1]
// printf("exe\n");
if(!x&&!y) return k=0,void();
if(!x){
add(gv,tr[y].sum),mule(y,gu),k=y;
return;
}
if(!y){
add(gu,tr[x].sum),mule(x,gv),k=x;
return;
}
if(l==r){
int fu=tr[x].sum,fv=tr[y].sum;
add(gv,fv),
tr[x].sum=adc(1ll*tr[x].sum*gv%mod,1ll*gu*fv%mod),add(gu,fu);
k=x;
return;
}
int mid=(l+r)>>1;
pushdown(x),pushdown(y);
merge(lc(k),lc(x),lc(y),l,mid,gu,gv);
merge(rc(k),rc(x),rc(y),mid+1,r,gu,gv);
pushup(k);
return;
}
int query(int k,int l,int r,int L,int R){
if(!k) return 0;
if(L<=l&&r<=R) return tr[k].sum;
pushdown(k);
int mid=(l+r)>>1,res=0;
if(L<=mid) add(res,query(lc(k),l,mid,L,R));
if(mid<R) add(res,query(rc(k),mid+1,r,L,R));
return res;
}
void dfs(int i,int fa){
int md=0,su,sv;
dep[i]=dep[fa]+1;
for(auto v:lim[i]) md=std::max(md,dep[v]);
change(rt[i],0,n,md,1);
for(auto v:G[i]){
if(v==fa) continue;
dfs(v,i),su=0,sv=query(rt[v],0,n,0,dep[i]);
merge(rt[i],rt[i],rt[v],0,n,su,sv);
}
return;
}
int main(void){
n=read();
for(int i=1;i<n;++i){
int u=read(),v=read();
G[u].push_back(v),G[v].push_back(u);
}
m=read();
while(m--){
int u=read(),v=read();
lim[v].push_back(u);
}
dfs(1,0);
printf("%d",query(rt[1],0,n,0,0));
return 0;
}
作者:Meatherm
出处:https://www.cnblogs.com/Meatherm/p/17596116.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
Buy me a cup of coffee ☕.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
2022-08-01 【瞎口胡】李超线段树