[CGOI-3] 灵气
题面
模拟赛时当暴力写的,结果 AC 了。
向大佬请教后发现复杂度竟是
该做法离线。好想也好写,不需要分类讨论。
思路
将题目中加/删点操作的集合记作
记
对每一个点
将所有加/删点操作离线下来。若一个点
拓扑排序。删掉一条边
在
考虑采用数据结构维护
复杂度
常见的线段树合并因为将
本题没有这样的性质。
假如存在
重复对出度大于
实现细节
-
因为存在复用,所以线段树合并时一定要新建版本再修改。我写的是标记永久化,如果
\rm{pushdown} 的话也要注意这个问题。 -
完结。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+7;
int n,q,rt[N],que[N],indeg[N],a[N],t[N],ans[N];
vector<int> G[N];
vector<pair<int,int>> Q[N];
namespace SGT
{
int tot;
struct node{int ls,rs,ans,tag;}tr[N*80];
#define ls(p) tr[p].ls
#define rs(p) tr[p].rs
inline void pushup(int p){tr[p].ans=tr[p].tag+max(tr[ls(p)].ans,tr[rs(p)].ans);}
void add(int &p,int L,int R,int x,int l=1,int r=q)
{
if(!p) p=++tot;
if(L<=l&&r<=R) return tr[p].ans+=x,tr[p].tag+=x,void();
int mid=(l+r)>>1;
if(L<=mid) add(ls(p),L,R,x,l,mid);
if(R>mid) add(rs(p),L,R,x,mid+1,r);
pushup(p);
}
void merge(int &p,int q)
{
if(!p||!q) return p|=q,void();
int o=++tot;tr[o]=tr[p],p=o;
merge(ls(p),ls(q));
merge(rs(p),rs(q));
tr[p].tag+=tr[q].tag;
pushup(p);
}
int query(int p,int L,int R,int l=1,int r=q)
{
if(!p) return 0;
if(L<=l&&r<=R) return tr[p].ans;
int mid=(l+r)>>1,res=0;
if(L<=mid) res=query(ls(p),L,R,l,mid);
if(R>mid) res=max(res,query(rs(p),L,R,mid+1,r));
return res+tr[p].tag;
}
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0);
int i,x,y,op,qcnt=0,hd,tl,u;
cin>>n>>q;
for(i=1;i<=n;++i) cin>>a[i];
for(i=1;i<n;++i)
{
cin>>x>>y;
G[x].push_back(y),++indeg[y];
}
for(i=1;i<=q;++i)
{
cin>>op>>x;
if(op==1) t[x]=i;
else if(op==2) SGT::add(rt[x],t[x],i-1,a[x]),t[x]=0;
else Q[x].emplace_back(i,++qcnt);
}
for(i=1;i<=n;++i)if(t[i]!=0) SGT::add(rt[i],t[i],q,a[i]);
//topo
hd=1,tl=0;
for(i=1;i<=n;++i)if(indeg[i]==0) que[++tl]=i;
while(hd<=tl)
{
u=que[hd++];
for(int v:G[u])
{
SGT::merge(rt[v],rt[u]);
if(!--indeg[v]) que[++tl]=v;
}
for(auto p:Q[u]) ans[p.second]=SGT::query(rt[u],1,p.first);
}
for(i=1;i<=qcnt;++i) cout<<ans[i]<<'\n';
return 0;
}