bzoj 3779 重组病毒 —— LCT+树状数组(区间修改+区间查询)
Posted zinn
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bzoj 3779 重组病毒 —— LCT+树状数组(区间修改+区间查询)相关的知识,希望对你有一定的参考价值。
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3779
RELEASE操作可以对应LCT的 access,RECENTER则是 makeroot;
考虑颜色数,把一条实边变成虚边,子树+1,虚变实子树-1;
但有换根操作,怎么维护子树?
也可以用 dfs 序线段树维护,其实换 rt 只是 splay 的根方向改变,对应的子树还是可以找到的;
注意虚边变实或实边变虚时要找子树,不是直接找那个儿子,而是找那个儿子所在 splay 的根;
然后这里 splay 的 reverse 就要打上标记的同时已经交换子树,否则找儿子时可能会错(?);
一开始写了树剖+线段树的复杂版本,总是不对...
#include<cstdio> #include<cstring> #include<algorithm> #define mid ((l+r)>>1) #define ls (x<<1) #define rs (x<<1|1) using namespace std; int const xn=1e5+5; int n,rt,pre[xn],c[xn][2],sta[xn],tp,rev[xn],hd[xn],ct,to[xn<<1],nxt[xn<<1]; int tim,fa[xn],dfn[xn],sum[xn<<1],lzy[xn<<1],top[xn],siz[xn],son[xn],dep[xn]; char dc[20]; int rd() { int ret=0,f=1; char ch=getchar(); while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=0; ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘)ret=ret*10+ch-‘0‘,ch=getchar(); return f?ret:-ret; } void add(int x,int y){to[++ct]=y; nxt[ct]=hd[x]; hd[x]=ct;} void turn(int x,int s,int len){sum[x]+=s*len; lzy[x]+=s;}//*len void pushdown(int x,int l,int r) { if(!lzy[x])return; turn(ls,lzy[x],mid-l+1); turn(rs,lzy[x],r-mid); lzy[x]=0; } void pushup(int x){sum[x]=sum[ls]+sum[rs];} void update(int x,int l,int r,int L,int R,int s) { if(L>R)return; if(l>=L&&r<=R){turn(x,s,r-l+1); return;} pushdown(x,l,r); if(mid>=L)update(ls,l,mid,L,R,s); if(mid<R)update(rs,mid+1,r,L,R,s); pushup(x); } int query(int x,int l,int r,int L,int R) { if(L>R)return 0; if(l>=L&&r<=R)return sum[x]; pushdown(x,l,r); int ret=0; if(mid>=L)ret+=query(ls,l,mid,L,R); if(mid<R)ret+=query(rs,mid+1,r,L,R); return ret; } void dfs(int x,int ff) { fa[x]=pre[x]=ff;//self splay dep[x]=dep[ff]+1; siz[x]=1; for(int i=hd[x],u;i;i=nxt[i]) if((u=to[i])!=ff) { dfs(u,x); siz[x]+=siz[u]; if(siz[u]>siz[son[x]])son[x]=u; } } void dfs2(int x) { dfn[x]=++tim; update(1,1,n,dfn[x],dfn[x],dep[x]); if(son[x])top[son[x]]=top[x],dfs2(son[x]); for(int i=hd[x],u;i;i=nxt[i]) if((u=to[i])!=son[x]&&u!=fa[x])top[u]=u,dfs2(u); } void torev(int x){rev[x]^=1; swap(c[x][0],c[x][1]);}// void reverse(int x) { if(!rev[x])return; //swap(c[x][0],c[x][1]); //rev[c[x][0]]^=1; rev[c[x][1]]^=1; if(c[x][0])torev(c[x][0]); if(c[x][1])torev(c[x][1]); rev[x]=0; } bool isroot(int x){return c[pre[x]][0]!=x&&c[pre[x]][1]!=x;} void rotate(int x) { int y=pre[x],z=pre[y],d=(c[y][1]==x); if(!isroot(y))c[z][c[z][1]==y]=x; pre[x]=z; pre[y]=x; pre[c[x][!d]]=y; c[y][d]=c[x][!d]; c[x][!d]=y; } void splay(int x) { //reverse! sta[tp=1]=x; for(int i=x;!isroot(i);i=pre[i])sta[++tp]=pre[i]; while(tp)reverse(sta[tp--]); while(!isroot(x)) { int y=pre[x],z=pre[y]; if(!isroot(y)) { if((c[y][0]==x)^(c[z][0]==y))rotate(x); else rotate(y); } rotate(x); } } int find(int x,int y)//y in xtree { //while(top[y]!=top[x])y=fa[top[y]];//? while(dep[x]<dep[top[y]]) { y=top[y]; if(fa[y]==x)return y; y=fa[y]; } //while(fa[y]!=x)y=fa[y]; //return y; return son[x];// } void change(int x,int v) { if(dfn[rt]>dfn[x]&&dfn[rt]<=dfn[x]+siz[x]-1) { int y=find(x,rt); update(1,1,n,1,dfn[y]-1,v); update(1,1,n,dfn[y]+siz[y],n,v); } else update(1,1,n,dfn[x],dfn[x]+siz[x]-1,v); } int findrt(int x) { while(c[x][0])x=c[x][0]; return x; } void access(int x) { bool fl=0; for(int t=0;x;x=pre[x]) { splay(x); if(c[x][1])change(findrt(c[x][1]),1);// c[x][1]=t; if(t)change(findrt(t),-1);// t=x; } } void makeroot(int x) { access(x); splay(x); torev(x);// } int main() { n=rd(); int m=rd(); for(int i=1,x,y;i<n;i++)x=rd(),y=rd(),add(x,y),add(y,x); rt=1; dfs(1,0); top[1]=1; dfs2(1); for(int i=1,x;i<=m;i++) { scanf("%s",dc); scanf("%d",&x); if(dc[2]==‘L‘)access(x); if(dc[2]==‘C‘)makeroot(x),rt=x; if(dc[2]==‘Q‘) { int ret=0,sz; if(dfn[rt]>dfn[x]&&dfn[rt]<=dfn[x]+siz[x]-1)//in subtree { int y=find(x,rt); sz=n-siz[y]; ret+=query(1,1,n,1,dfn[y]-1); ret+=query(1,1,n,dfn[y]+siz[y],n); } else if(rt==x)ret=query(1,1,n,1,n),sz=n;//!!! else ret=query(1,1,n,dfn[x],dfn[x]+siz[x]-1),sz=siz[x]; printf("ret=%d sz=%d ",ret,sz); printf("%.7f ",1.0*ret/sz); } } return 0; }
然后听说线段树会被卡,要写树状数组,于是干脆把那个未知错误的代码扔了;
但是...树状数组维护序列可以单点修改区间查询,维护差分序列可以区间修改单点查询,如何区间修改区间查询?
找题解,学到新知识了——区间修改区间查询的树状数组!
树状数组里放两个数组,一个维护差分序列 ( d[i] ),另一个维护 ( d[i]*i );
为什么这样?因为首先,要区间修改,只能维护差分数组;
考虑如何从差分数组得到原数组的前缀和 ( s[p] ),就是 ( sumlimits_{i=1}^{p} sumlimits_{j=1}^{i} d[j] )
发现一个 ( d[j] ) 被算了 ( p-j+1 ) 次,也就是 ( (p+1)-j ) 次;
所以只要维护 ( d[i] ) 和 ( d[i]*i ),到时候查询 ( p ) 位置的原序列前缀和,就是 ( (p+1)*sumlimits_{i=1}^{p} d[i] - sumlimits_{i=1}^{p}d[i]*i );
所以这个似乎很好用的样子;
一晚上栽在 splay 之前的一系列 reverse 上,又分不清 i 和 x 了呵呵,然而实际上这题做了一天。
代码如下:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef long long ll; int const xn=1e5+5; int n,m,rt,hd[xn],ct,to[xn<<1],nxt[xn<<1],sta[xn],top,rev[xn],dep[xn]; int tim,fa[xn][20],pre[xn],c[xn][2],dfn[xn],ed[xn]; ll s[xn],g[xn]; int rd() { int ret=0,f=1; char ch=getchar(); while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=0; ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘)ret=ret*10+ch-‘0‘,ch=getchar(); return f?ret:-ret; } void add(int x,int y){to[++ct]=y; nxt[ct]=hd[x]; hd[x]=ct;} void update(int x,ll v){for(int i=x;i<=n;i+=(i&-i))s[i]+=v,g[i]+=x*v;}//g[i]+=x*v ll query(int x) { if(x>n)return 0; ll s1=0,s2=0; for(int i=x;i;i-=(i&-i))s1+=s[i],s2+=g[i]; return (x+1)*s1-s2; } void dfs(int x,int ff) { pre[x]=fa[x][0]=ff; for(int i=1;fa[fa[x][i-1]][i-1];i++)fa[x][i]=fa[fa[x][i-1]][i-1]; dfn[x]=++tim; dep[x]=dep[ff]+1; for(int i=hd[x],u;i;i=nxt[i]) if((u=to[i])!=ff)dfs(u,x); ed[x]=tim; update(dfn[x],1); update(ed[x]+1,-1); } bool isroot(int x){return c[pre[x]][0]!=x&&c[pre[x]][1]!=x;} void rever(int x) { rev[x]^=1; swap(c[x][0],c[x][1]); } void pushdn(int x) { if(!rev[x])return; rever(c[x][0]); rever(c[x][1]); rev[x]=0; } void rotate(int x) { int y=pre[x],z=pre[y],d=(c[y][1]==x); if(!isroot(y))c[z][c[z][1]==y]=x; pre[x]=z; pre[y]=x; pre[c[x][!d]]=y; c[y][d]=c[x][!d]; c[x][!d]=y; } void splay(int x) { sta[top=1]=x; for(int i=x;!isroot(i);i=pre[i])sta[++top]=pre[i];//i!!! while(top)pushdn(sta[top--]); while(!isroot(x)) { int y=pre[x],z=pre[y]; if(!isroot(y)) ((c[y][0]==x)^(c[z][0]==y))?rotate(x):rotate(y); rotate(x); } } int find(int x,int y)//y in x‘s subtree { for(int i=19;i>=0;i--) if(dep[fa[y][i]]>dep[x])y=fa[y][i]; return y; } void change(int x,int v) { while(c[x][0])pushdn(x),x=c[x][0]; if(rt==x)update(1,v); else if(dfn[rt]>dfn[x]&&dfn[rt]<=ed[x]) { int y=find(x,rt); update(1,v); update(dfn[y],-v); update(ed[y]+1,v); } else update(dfn[x],v),update(ed[x]+1,-v); } void access(int x) { for(int t=0;x;x=pre[x]) { splay(x); if(c[x][1])change(c[x][1],1); c[x][1]=t; if(t)change(t,-1); t=x; } } void makeroot(int x) { access(x); splay(x); rever(x); } double getans(int x) { if(rt==x)return 1.0*query(n)/n; if(dfn[rt]>dfn[x]&&dfn[rt]<=ed[x]) { int y=find(x,rt); ll ret=query(n)-query(ed[y])+query(dfn[y]-1); return 1.0*ret/(n-(ed[y]-dfn[y]+1)); } ll ret=query(ed[x])-query(dfn[x]-1); return 1.0*ret/(ed[x]-dfn[x]+1); } char dc[20]; int main() { n=rd(); m=rd(); for(int i=1,x,y;i<n;i++)x=rd(),y=rd(),add(x,y),add(y,x); dfs(1,0); for(int i=1,x;i<=m;i++) { scanf("%s",dc); x=rd(); if(dc[2]==‘L‘)access(x); if(dc[2]==‘C‘)makeroot(x),rt=x; if(dc[2]==‘Q‘)printf("%.10f ",getans(x)); } return 0; }
以上是关于bzoj 3779 重组病毒 —— LCT+树状数组(区间修改+区间查询)的主要内容,如果未能解决你的问题,请参考以下文章
BZOJ 3779 重组病毒 LCT+线段树(维护DFS序)