1. 程式人生 > >bzoj 3779 重組病毒 —— LCT+樹狀陣列(區間修改+區間查詢)

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\n",ret,sz); printf("%.7f\n",1.0*ret/sz); } } return 0; }

然後聽說線段樹會被卡,要寫樹狀陣列,於是乾脆把那個未知錯誤的程式碼扔了;

但是...樹狀陣列維護序列可以單點修改區間查詢,維護差分序列可以區間修改單點查詢,如何區間修改區間查詢?

找題解,學到新知識了——區間修改區間查詢的樹狀陣列!

樹狀數組裡放兩個陣列,一個維護差分序列 \( d[i] \),另一個維護 \( d[i]*i \);

為什麼這樣?因為首先,要區間修改,只能維護差分陣列;

考慮如何從差分陣列得到原陣列的字首和 \( s[p] \),就是 \( \sum\limits_{i=1}^{p} \sum\limits_{j=1}^{i} d[j] \)

發現一個 \( d[j] \) 被算了 \( p-j+1 \) 次,也就是 \( (p+1)-j \) 次;

所以只要維護 \( d[i] \) 和 \( d[i]*i \),到時候查詢 \( p \) 位置的原序列字首和,就是 \( (p+1)*\sum\limits_{i=1}^{p} d[i] - \sum\limits_{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\n",getans(x));
    }
  return 0;
}