[WC/CTS2024] 线段树 题解
纪念一下场切题(整个OI生涯中最高光的一次)。
题意:给定一棵(分点不一定为中点)的线段树,给定若干个询问区间,问有多少个线段树上结点的集合,知道了这些结点对应的区间和就可以知道任何一个询问区间的和。
从询问区间开始考虑。会发现可以把所有 分成若干个集合,只要知道每个集合的和就可以知道所有询问区间的和。具体地,“被覆盖情况”相同的 是在同一个集合中的。特别的,可能有一些 没有被任何询问区间覆盖,这些 分到一个集合,它们的和可以不用求出。这个集合具体的分法可以用一个哈希处理,给每个区间赋一个权值,区间中每个点加上这个权值,就可以认为权值相同的点属于一个集合。
再考虑到线段树上的区间。可以认为我们标记了一些节点,知道了它们对应区间的和。同样的,通过上面的分类方法可以把所有 拆成若干个集合,知道了每个集合的和(以及有一个集合不知道)。进一步地,利用这个树形结构,会发现“祖先中自下而上第一个标记结点”相同的 是分在同一个集合里的。
考虑两组分类的关系。我们需要满足询问,所以第一种分类方式中不同集合的两个 在第二种分类方式中不能分在同一集合。也就是说第一组集合进一步拆分得到了第二组集合。容易发现这个条件是充要的。
下面来考虑一下特殊性质。特殊性质相当于第一种分类方式有 个集合 ,它们的和都需要知道。从刚才说明的第二种分类的判断方法入手,考虑做一个树形 DP,自下而上确定。根据刚才的充要条件,对某个点 ,确定了它的子树的选择情况后,子树内的所有 至多有一个的分类(即第一个祖先)还没有确定。 那么就可以设 表示子树 内所有 的分类都确定的方案数, 表示子树 内有一个 的分类还没有确定的方案数。设 的左右儿子分别为 ,有
初始值是对于叶子结点 ,。答案是 。这样就可以通过特殊性质。
再来处理一般情况。容易把刚才的性质推广:一棵子树完全确定后,至多有一类结点的分类(第一个祖先)还没有确定。那么就可以设计一个二维DP: 表示子树 内的方案确定后,第 个集合的点的分类还没有确定。特别地, 表示都已经确定, 表示这一类的和可以不用求,也就是它们可以没有“第一个标记的祖先”。转移为
初始值是对于叶子结点 ,如果它在第 类,有 。答案是 。直接做这个 DP,复杂度是 ,结合特殊性质可以获得 分。
最后 分,只需要记录 中每个非零数,做启发式合并即可。具体的,需要维护一个乘法标记,以及 。使用 map
实现,时间复杂度是 。
中间有一步除法,可能遇到除以 的情况,所以需要特殊处理(是容易的)。场上当然没有考虑到,不过良心出题人 jiangly 在 WC 的数据里并没有卡,赞美良心出题人!
代码是赛后补的。
upd on 2024.3.19:更新了代码,现在能够通过 CTS 的数据了。
#include<bits/stdc++.h> using namespace std; typedef unsigned long long ull; mt19937_64 rnd(time(0)); const int N=4e5+5,P=998244353; int qpow(int a,int b=P-2){ int c=1; for(;b;b>>=1,a=1ll*a*a%P) if(b&1)c=1ll*c*a%P; return c; } int n,m,tot=1,typ[N];ull h[2][N]; struct node{int ls,rs,l,r;}tr[N]; void build(int p,int l,int r){ tr[p].l=l;tr[p].r=r; if(l==r)return; int mid;scanf("%d",&mid); tr[p].ls=++tot;build(tot,l,mid); tr[p].rs=++tot;build(tot,mid+1,r); } vector<pair<ull,ull> > vec; map<int,int> dp[N];int to[N],mul[N],sum[N]; int main(){ scanf("%d%d",&n,&m);build(1,1,n); for(int i=1,l,r;i<=m;i++){ scanf("%d%d",&l,&r);++l;++r; ull v0=rnd(),v1=rnd(); h[0][l]+=v0;h[0][r]-=v0; h[1][l]+=v1;h[1][r]-=v1; } for(int i=1;i<=n;i++){ h[0][i]+=h[0][i-1];h[1][i]+=h[1][i-1]; vec.push_back({h[0][i],h[1][i]}); } vec.push_back({0,0});sort(vec.begin(),vec.end()); for(int i=1;i<=n;i++)typ[i]=lower_bound(vec.begin(),vec.end(), make_pair(h[0][i],h[1][i]))-vec.begin()+1; for(int i=2*n-1;i>=1;i--){ if(!tr[i].ls){ dp[i][0]=dp[i][typ[tr[i].l]]=1; to[i]=i;mul[i]=sum[i]=1;continue; } int x=to[tr[i].ls],y=to[tr[i].rs]; if(dp[x].size()<dp[y].size())swap(x,y); int vl=dp[x][0],vr=dp[y][0]; if(vr==0){ to[i]=i;mul[i]=1ll*mul[x]*mul[y]%P; for(auto it:dp[y]){ int c=it.first,f=it.second; if(c==0)continue; dp[i][c]=1ll*(vl+dp[x][c])*f%P; sum[i]=(sum[i]+dp[i][c])%P; } dp[i][0]=sum[i]; } else{ mul[x]=1ll*mul[x]*mul[y]%P*vr%P; dp[x][0]=(vl+vl)%P;to[i]=x;vr=qpow(vr); for(auto it:dp[y]){ int c=it.first,f=it.second; if(c==0)continue; int tmp=1ll*f*(dp[x][c]+vl)%P*vr%P; dp[x][c]=(dp[x][c]+tmp)%P; sum[x]=(sum[x]+tmp)%P; } dp[x][0]=(dp[x][0]+sum[x])%P; } } int ans=(dp[to[1]][0]+dp[to[1]][1])%P; ans=1ll*ans*mul[to[1]]%P; printf("%d\n",ans); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具