【BZOJ】3779 重组病毒
【算法】Link-Cut Tree+線段樹(維護DFS序)
【題解】整整三天……T_T
這篇題解比較資瓷:permui
這道題雖然樹形態沒有變化,但用lct寫的原因在于把題目中的操作一進行了神轉化:每條重鏈表示一種顏色,點到根的顏色數=經過的輕鏈數+1。
詢問一個點的子樹所有結點到根的代價和(的平均數),子樹問題考慮使用dfs序維護。有要支持子樹整體的加減,所以用線段樹維護DFS序。
操作一轉化之后就相當于access操作(x到根染成一種顏色==x到根在同一重鏈),過程中輕變重子樹-1,重變輕子樹+1,這里的子樹是相對于新根的,做法見下。
操作二換根是關鍵。題目條件剛好先做了access(x)操作,于是我們就可以很方便地換根。問題在于DFS序根據的樹形態從一開始就固定下來,那么根據新根root和查詢點x在樹上的位置關系可以分類討論:
1.root=x,整棵樹。
2.root在x的子樹上,整棵樹除去root所在子樹。
3.x在root的子樹上,原x子樹。
【注意】
1.代價和很大,long long。
2.線段樹放越界:if(l>r)return;
下傳之前判斷是否葉子結點
mid是seg的l和r的中點
3.無向邊數組開2倍。
3.鄰接表記得判父親。
4.利用DFS序嵌套關系判斷子結點。
5.splay上訪問前要先下傳。
#include<cstdio> #include<cstring> #include<cctype> #include<algorithm> using namespace std; const int maxn=100010; int f[maxn],t[maxn][2],dfsnum=0,first[maxn],second[maxn],next[maxn],tot,deep[maxn],g[maxn],root,n,m,father[maxn]; struct edge{int u,v,from;}e[maxn*3];//無向邊數組開兩倍! struct tree{int l,r;long long sum,delta;}tr[maxn*3]; int read() {char c;int s=0;while(!isdigit(c=getchar()));do{s=s*10+c-'0';}while(isdigit(c=getchar()));return s; } void insert(int u,int v) {tot++;e[tot].u=u;e[tot].v=v;e[tot].from=next[u];next[u]=tot;} void seg_build(int k,int l,int r) {tr[k].l=l;tr[k].r=r;if(l==r){tr[k].sum=0;return;}int mid=(l+r)>>1;seg_build(k<<1,l,mid);seg_build(k<<1|1,mid+1,r); } void seg_ins(int k,int x) {tr[k].sum+=1ll*(tr[k].r-tr[k].l+1)*x;} void seg_pushdown(int k) {if(tr[k].delta){tr[k<<1].delta+=tr[k].delta;tr[k<<1|1].delta+=tr[k].delta;seg_ins(k<<1,tr[k].delta);seg_ins(k<<1|1,tr[k].delta);tr[k].delta=0;} } void seg_insert(int k,int l,int r,int x) {if(l>r)return;//woc!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // if(tr[k].l==tr[k].r&&l!=tr[k].l&&r!=tr[k].r){return;}//這樣寫是錯的!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1 if(l<=tr[k].l&&tr[k].r<=r){seg_ins(k,x);tr[k].delta+=1ll*x;return;}else{if(tr[k].l==tr[k].r)return;//防止非法訪問!!!但是其實在區間的中間(l+1)也可能出現葉子結點的,所以不能一概而論,只能在這里判斷。 seg_pushdown(k);int mid=(tr[k].l+tr[k].r)>>1;if(l<=mid)seg_insert(k<<1,l,r,x);if(r>mid)seg_insert(k<<1|1,l,r,x);tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;} } long long seg_query(int k,int l,int r) {if(l>r)return 0;//woc!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // if(tr[k].l==tr[k].r&&l!=tr[k].l&&r!=tr[k].r)return 0;//錯誤寫法! if(l<=tr[k].l&&tr[k].r<=r)return tr[k].sum;if(tr[k].l==tr[k].r)return 0; seg_pushdown(k);int mid=(tr[k].l+tr[k].r)>>1;//mid是seg的中點! long long sums=0;if(l<=mid)sums=seg_query(k<<1,l,r);if(r>mid)sums+=seg_query(k<<1|1,l,r);return sums; } void dfs(int x,int fa) {father[x]=fa;f[x]=fa;//開始時每個點自成重鏈,但是初始根為1,所以父子關系已經串好了。 first[x]=++dfsnum;deep[x]=deep[fa]+1;seg_insert(1,first[x],first[x],deep[x]);for(int i=next[x];i;i=e[i].from)if(e[i].v!=fa)//判父親!!! {//這棵樹的父親和splay的父親不能混為一談。 dfs(e[i].v,x);}second[x]=dfsnum; } bool isroot(int x) {return !x||(t[f[x]][0]!=x&&t[f[x]][1]!=x);}//0也認為它是splay的根。 void pushdown(int x) {if(g[x]){g[t[x][0]]^=1;g[t[x][1]]^=1;swap(t[x][0],t[x][1]);g[x]=0;} } void rotate(int x) {int k=x==t[f[x]][1];int y=f[x];t[y][k]=t[x][!k];f[t[x][!k]]=y;if(!isroot(y))t[f[y]][y==t[f[y]][1]]=x;f[x]=f[y];f[y]=x;t[x][!k]=y; } int cnt,N[maxn]; void splay(int x) {cnt=0;int y=x;while(!isroot(y))N[++cnt]=y,y=f[y];pushdown(y);for(int i=cnt;i>=1;i--)pushdown(N[i]);while(!isroot(x)){if(isroot(f[x])){rotate(x);return;}int X=x==t[f[x]][1],Y=f[x]==t[f[f[x]]][1];if(X^Y)rotate(x),rotate(x);else rotate(f[x]),rotate(x);} } bool inson(int x,int y)//x在y的子樹內 {return first[x]>=first[y]&&second[x]<=second[y];} int wson(int x,int y)//x在y的哪個兒子的子樹內 {for(int i=next[y];i;i=e[i].from)if(e[i].v!=father[y]){if(first[x]>=first[e[i].v]&&second[x]<=second[e[i].v])return e[i].v;}return 0; } void lct_insert(int x,int num) {if(x==root)seg_insert(1,1,n,num);elseif(inson(root,x)){int p=wson(root,x);seg_insert(1,1,first[p]-1,num);seg_insert(1,second[p]+1,n,num);}else seg_insert(1,first[x],second[x],num); } int top(int x) {pushdown(x);while(t[x][0])x=t[x][0],pushdown(x);//訪問子節點要下傳啊!!! return x; } void access(int x) {int y=0;while(x){splay(x);if(t[x][1])lct_insert(top(t[x][1]),1);//隨時要注意判斷結點是否存在! if(y)lct_insert(top(y),-1);//root一定是主鏈的根(最左端結點),往主鏈方向靠近的話,重鏈頂部結點的子樹就能自然地覆蓋包括整條鏈。t[x][1]=y; y=x;x=f[x];} } void center(int x) {splay(x);//必須splay才能定位到主鏈,方便翻轉。 root=x;g[x]^=1;//使x成為主鏈的根,因為access中已經splay了使x成為主鏈splay的根節點,對x操作就是對主鏈操作。 } double query(int x) {if(x==root){return 1.0*seg_query(1,1,n)/n;}elseif(inson(root,x)){int p=wson(root,x);return 1.0*(seg_query(1,1,first[p]-1)+seg_query(1,second[p]+1,n))/(n-(second[p]-first[p]+1));}else return 1.0*seg_query(1,first[x],second[x])/(second[x]-first[x]+1); } char s[20]; int main() {n=read();m=read();int u,v;for(int i=1;i<n;i++){u=read(),v=read();insert(u,v);insert(v,u);}root=1;//初值! seg_build(1,1,n);dfs(1,0);int x;for(int i=1;i<=m;i++){scanf("%s%d",s,&x);if(s[2]=='Q')printf("%.10lf\n",query(x)); else{access(x);if(s[2]=='C')center(x);}}return 0; } View Code?
轉載于:https://www.cnblogs.com/onioncyc/p/6965308.html
總結
以上是生活随笔為你收集整理的【BZOJ】3779 重组病毒的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从扁平到立体:Windows 10 图标
- 下一篇: jQuery Validate 提交表单