P3313 [SDOI2014]旅行
树剖+动态开点线段树
对每种宗教开一个线段树,用来维护区间和,区间最大值
普通的线段树空间不够,所以要动态开点
因为宗教会改变,所以要有删除操作和插入操作
比如城市1从信仰a变成信仰b,那就把a的线段树上城市1删掉,在b线段树上插入城市1
询问就只要询问与旅行者同宗教的值就好了
怎么实现也不难,看看代码就懂了
然后我还搞了个垃圾回收的操作
把删掉的节点编号记录一下,如果有新增节点,编号优先用以前的
很容易实现
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<queue> using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const int N=2e5+7; int n,m,w[N],c[N]; int fir[N<<1],from[N<<1],to[N<<1],cntt; inline void add(int &a,int &b) { from[++cntt]=fir[a]; fir[a]=cntt; to[cntt]=b; } //以下为树剖预处理 int dep[N],sz[N],fa[N],son[N]; void dfs1(int x,int f) { dep[x]=dep[f]+1; sz[x]=1; fa[x]=f; int mxsz=0; for(int i=fir[x];i;i=from[i]) { int &v=to[i]; if(v==f) continue; dfs1(v,x); sz[x]+=sz[v]; if(sz[v]>mxsz) mxsz=sz[v],son[x]=v; } } int id[N],Top[N],tot; void dfs2(int x,int topp) { id[x]=++tot; Top[x]=topp; if(!son[x]) return; dfs2(son[x],topp); for(int i=fir[x];i;i=from[i]) { int &v=to[i]; if(v==fa[x]||v==son[x]) continue; dfs2(v,v); } } //以上为树剖预处理 //--------------------------线段树---------------------------------- struct node { int sum,mx,lc,rc;//因为动态开点所以要记录左右儿子 node() { sum=mx=lc=rc=0; } }t[N<<4]; int cnt,rt[N];//rt是每个宗教的根节点 queue <int> q;//存回收的编号 inline void pushup(int &o)//更新当前节点 { t[o].sum=t[t[o].lc].sum+t[t[o].rc].sum; t[o].mx=max(t[t[o].lc].mx,t[t[o].rc].mx); } inline void clr(int &o)//回收垃圾 { t[o].sum=t[o].mx=t[o].lc=t[o].rc=0; q.push(o); o=0; } void ins(int &o,int l,int r,int &v/*值*/,int &pos/*位置*/)//插入 or 更新 { if(!o)//如果还没节点就新建一个 { if(!q.empty()) o=q.front(),q.pop();//垃圾回收利用 else o=++cnt; } if(l==r)//如果到叶子节点就更新并返回 { t[o].sum=t[o].mx=v; return; } int mid=l+r>>1; if(pos<=mid) ins(t[o].lc,l,mid,v,pos); else ins(t[o].rc,mid+1,r,v,pos); pushup(o);//更新当前节点 } void erase(int &o,int l,int r,int &pos)//删除 { if(l==r) { clr(o); return; } int mid=l+r>>1; if(pos<=mid) erase(t[o].lc,l,mid,pos); else erase(t[o].rc,mid+1,r,pos); if(t[o].lc||t[o].rc) pushup(o);//如果还有儿子就更新值 else clr(o);//不然一起删了 } int query_sum(int o,int l,int r,int &ql,int &qr)//询问区间sum { if(!o) return 0; if(l>=ql&&r<=qr) return t[o].sum; int mid=l+r>>1,res=0; if(ql<=mid) res+=query_sum(t[o].lc,l,mid,ql,qr); if(qr>mid) res+=query_sum(t[o].rc,mid+1,r,ql,qr); return res; } int query_max(int o,int l,int r,int &ql,int &qr)//询问区间max { if(!o) return 0; if(l>=ql&&r<=qr) return t[o].mx; int mid=l+r>>1,res=0; if(ql<=mid) res=max(res,query_max(t[o].lc,l,mid,ql,qr)); if(qr>mid) res=max(res,query_max(t[o].rc,mid+1,r,ql,qr)); return res; } //----------------------------线段树---------------------------------- void change_bel(int x,int y)//改变信仰 { erase(rt[c[x]],1,n,id[x]); c[x]=y; ins(rt[c[x]],1,n,w[x],id[x]); } void change_val(int x,int y)//改变价值 { w[x]=y; ins(rt[c[x]],1,n,w[x],id[x]); } void Q_sum(int x,int y)//询问树上两点间路径的同宗教的sum { int res=0,be=c[x];//旅行者的宗教 while(Top[x]!=Top[y])//正常的树剖询问 { if(dep[Top[x]]<dep[Top[y]]) swap(x,y); res+=query_sum(rt[be],1,n,id[Top[x]],id[x]); x=fa[Top[x]]; } if(dep[x]<dep[y]) swap(x,y); res+=query_sum(rt[be],1,n,id[y],id[x]); printf("%d\n",res); } void Q_max(int x,int y)//跟上面差不多 { int res=0,be=c[x]; while(Top[x]!=Top[y]) { if(dep[Top[x]]<dep[Top[y]]) swap(x,y); res=max(res,query_max(rt[be],1,n,id[Top[x]],id[x])); x=fa[Top[x]]; } if(dep[x]<dep[y]) swap(x,y); res=max(res,query_max(rt[be],1,n,id[y],id[x])); printf("%d\n",res); } int main() { n=read(); m=read(); for(int i=1;i<=n;i++) w[i]=read(),c[i]=read(); int a,b; for(int i=1;i<n;i++) a=read(),b=read(),add(a,b),add(b,a); dfs1(1,1); dfs2(1,1); for(int i=1;i<=n;i++) ins(rt[c[i]],1,n,w[i],id[i]);//处理好初始状态 char ch[5]; for(int i=1;i<=m;i++) { scanf("%s",ch); a=read(); b=read(); if(ch[0]=='C') { if(ch[1]=='C') change_bel(a,b); else change_val(a,b); } else { if(ch[1]=='S') Q_sum(a,b); else Q_max(a,b); } } return 0; }