【bzoj 3779】重組病毒
阿新 • • 發佈:2017-06-25
在操作 答案 opened 不同 family req preview edge -1
實驗在一個封閉的局域網內進行。局域網內有n臺計算機,編號為1~n。一些計算機之間通過網線直接相連,形成樹形的結構。局域網中有一臺特殊的計算機,稱之為核心計算機。根據一些初步的研究,研究員們擬定了一個一共m步的實驗。實驗開始之前,核心計算機的編號為1,每臺計算機中都有病毒的一個變種,而且每臺計算機中的變種都不相同。實驗中的每一步會是下面中的一種操作:
1、 RELEASE x
在編號為x的計算機中植入病毒的一個新變種。這個變種在植入之前不存在於局域網中。
2、 RECENTER x
將核心計算機改為編號為x的計算機。但是這個操作會導致原來核心計算機中的病毒產生新變種,並感染過來。換言之,假設操作前的核心計算機編號為y,相當於在操作後附加了一次RELEASE y的操作。
根據研究的結論,在植入一個新變種時,病毒會在局域網中搜索核心計算機的位置,並沿著網絡中最短的路徑感染過去。
而第一輪實驗揭露了一個驚人的真相:病毒的不同變種是互斥的。新變種在感染一臺已經被舊變種感染的電腦時,會把舊變種完全銷毀之後再感染。但研究員發現了實現過程中的漏洞。如果新變種在感染過程中尚未銷毀過這類舊變種,需要先花費1單位時間分析舊變種,才能銷毀。如果之前銷毀過這類舊變種,就可以認為銷毀不花費時間。病毒在兩臺計算機之間的傳播亦可認為不花費時間。
研究員對整個感染過程的耗時特別感興趣,因為這是消滅病毒的最好時機。於是在m步實驗之中,研究員有時還會做出如下的詢問:
3、 REQUEST x
詢問如果在編號為x的計算機的關鍵集合中的計算機中植入一個新變種,平均感染時間為多長。編號為y的計算機在編號為x的計算機的關鍵集合中,當且僅當從y沿網絡中的最短路徑感染到核心計算機必須經過x。由於有RECENTER操作的存在,這個集合並不一定是始終不變的。
至此,安全機構認為已經不需要實際的實驗了,於是他們拜托你編寫一個程序,模擬實驗的結果,並回答所有的詢問。
接下來n-1行,每行包含兩個整數x和y,表示局域網中編號為x和y的計算機之間有網線直接相連。
接下來m行,每行包含一個操作或者詢問,格式如問題描述中所述。
Description
黑客們通過對已有的病毒反編譯,將許多不同的病毒重組,並重新編譯出了新型的重組病毒。這種病毒的繁殖和變異能力極強。為了阻止這種病毒傳播,某安全機構策劃了一次實驗,來研究這種病毒。實驗在一個封閉的局域網內進行。局域網內有n臺計算機,編號為1~n。一些計算機之間通過網線直接相連,形成樹形的結構。局域網中有一臺特殊的計算機,稱之為核心計算機。根據一些初步的研究,研究員們擬定了一個一共m步的實驗。實驗開始之前,核心計算機的編號為1,每臺計算機中都有病毒的一個變種,而且每臺計算機中的變種都不相同。實驗中的每一步會是下面中的一種操作:
1、 RELEASE x
在編號為x的計算機中植入病毒的一個新變種。這個變種在植入之前不存在於局域網中。
2、 RECENTER x
將核心計算機改為編號為x的計算機。但是這個操作會導致原來核心計算機中的病毒產生新變種,並感染過來。換言之,假設操作前的核心計算機編號為y,相當於在操作後附加了一次RELEASE y的操作。
根據研究的結論,在植入一個新變種時,病毒會在局域網中搜索核心計算機的位置,並沿著網絡中最短的路徑感染過去。
而第一輪實驗揭露了一個驚人的真相:病毒的不同變種是互斥的。新變種在感染一臺已經被舊變種感染的電腦時,會把舊變種完全銷毀之後再感染。但研究員發現了實現過程中的漏洞。如果新變種在感染過程中尚未銷毀過這類舊變種,需要先花費1單位時間分析舊變種,才能銷毀。如果之前銷毀過這類舊變種,就可以認為銷毀不花費時間。病毒在兩臺計算機之間的傳播亦可認為不花費時間。
研究員對整個感染過程的耗時特別感興趣,因為這是消滅病毒的最好時機。於是在m步實驗之中,研究員有時還會做出如下的詢問:
3、 REQUEST x
詢問如果在編號為x的計算機的關鍵集合中的計算機中植入一個新變種,平均感染時間為多長。編號為y的計算機在編號為x的計算機的關鍵集合中,當且僅當從y沿網絡中的最短路徑感染到核心計算機必須經過x。由於有RECENTER操作的存在,這個集合並不一定是始終不變的。
至此,安全機構認為已經不需要實際的實驗了,於是他們拜托你編寫一個程序,模擬實驗的結果,並回答所有的詢問。
Input
輸入的第一行包含兩個整數n和m,分別代表局域網中計算機的數量,以及操作和詢問的總數。接下來n-1行,每行包含兩個整數x和y,表示局域網中編號為x和y的計算機之間有網線直接相連。
接下來m行,每行包含一個操作或者詢問,格式如問題描述中所述。
Output
對於每個詢問,輸出一個實數,代表平均感染時間。輸出與答案的絕對誤差不超過 10^(-6)時才會被視為正確。Sample Input
8 6
1 2
1 3
2 8
3 4
3 5
3 6
4 7
REQUEST 7
RELEASE 3
REQUEST 3
RECENTER 5
RELEASE 2
REQUEST 1
Sample Output
4.0000000000
2.0000000000
1.3333333333
HINT
N<=100000 ,M<=100000
調了幾個世紀終於調出來了……LCT+dfs序線段樹。
令每個點權值為這個點到根的路徑上虛邊數+1,一開始時都是虛邊,點權即為深度。
操作1可以神轉換為access。在access過程中,當前節點的右兒子所代表的子樹整體權值+1(因為虛邊+1),而即將拼接過來的子樹整體權值-1。
操作2因為有換根操作,所以需要分類討論一波(以下結論畫圖易得):
1. x=root,查詢整棵樹;
2. root不在x的子樹內,查詢原樹中x的子樹;
3. root在x的子樹內,查詢整棵樹去除掉包含root的以x的親兒子為根的子樹。
然後就可以開始瞎搞啦?
(順便吐槽一句,結構體好醜啊T_T本來以為寫結構體會快一些的,其實……仿佛差不多
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=100010; 6 int n,m,x,y,cnt,dfsn,rt=1;//註意初始化rt為1 7 int head[N],st[N]; 8 struct edge{int u,v,next;}e[N*2];//鄰接表 9 struct leaf{int l,r;long long sum,tag;}t[N*4];//線段樹 10 struct node{int p,fa,c[2],deep,in,out;bool rev;}tr[N];//splay 11 int read() 12 { 13 int x=0,f=1;char c=getchar(); 14 while(c<‘0‘||c>‘9‘){if(c==‘-‘)f=-1;c=getchar();} 15 while(c>=‘0‘&&c<=‘9‘){x=x*10+c-‘0‘;c=getchar();} 16 return x*f; 17 } 18 void ins(int a,int b){cnt++;e[cnt].u=a;e[cnt].v=b;e[cnt].next=head[a];head[a]=cnt;} 19 void insert(int a,int b){ins(a,b);ins(b,a);} 20 void qadd(int num,long long w) 21 { 22 t[num].tag+=w; 23 t[num].sum+=(long long)(t[num].r-t[num].l+1)*w; 24 } 25 void pushup(int num){t[num].sum=t[num<<1].sum+t[num<<1|1].sum;} 26 void pushdown(int num)//線段樹下傳tag 27 { 28 if(!t[num].tag)return; 29 qadd(num<<1,t[num].tag); 30 qadd(num<<1|1,t[num].tag); 31 t[num].tag=0; 32 } 33 bool isroot(int k){return !k||(tr[tr[k].fa].c[0]!=k&&tr[tr[k].fa].c[1]!=k);} 34 void down(int k)//下傳翻轉標記 ,註意不要跟pushdown弄混 35 { 36 if(tr[k].rev) 37 { 38 int l=tr[k].c[0],r=tr[k].c[1]; 39 tr[k].rev^=1;tr[l].rev^=1;tr[r].rev^=1; 40 swap(tr[k].c[0],tr[k].c[1]); 41 } 42 } 43 void rotate(int x) 44 { 45 int y=tr[x].fa,z=tr[y].fa,l,r; 46 if(tr[y].c[0]==x)l=0;else l=1;r=l^1; 47 if(!isroot(y)){if(tr[z].c[0]==y)tr[z].c[0]=x;else tr[z].c[1]=x;} 48 tr[x].fa=z;tr[y].fa=x;tr[tr[x].c[r]].fa=y; 49 tr[y].c[l]=tr[x].c[r];tr[x].c[r]=y; 50 } 51 void splay(int x) 52 { 53 int top=0;st[++top]=x; 54 for(int i=x;!isroot(i);i=tr[i].fa)st[++top]=tr[i].fa; 55 for(int i=top;i;i--)down(st[i]); 56 while(!isroot(x)) 57 { 58 int y=tr[x].fa,z=tr[y].fa; 59 if(!isroot(y)) 60 { 61 if((tr[y].c[0]==x)^(tr[z].c[0]==y))rotate(x); 62 else rotate(y); 63 } 64 rotate(x); 65 } 66 } 67 void build(int num,int l,int r) 68 { 69 t[num].l=l;t[num].r=r; 70 if(l==r)return; 71 int mid=(l+r)>>1; 72 build(num<<1,l,mid);build(num<<1|1,mid+1,r); 73 } 74 void add(int num,int l,int r,long long w)//線段樹區間加 75 { 76 if(l>r)return; 77 if(l<=t[num].l&&t[num].r<=r){qadd(num,w);return;} 78 if(t[num].l==t[num].r)return; 79 pushdown(num); 80 int mid=(t[num].l+t[num].r)>>1; 81 if(l<=mid)add(num<<1,l,r,w); 82 if(r>mid)add(num<<1|1,l,r,w); 83 pushup(num); 84 } 85 void dfs(int now,int last) 86 { 87 tr[now].p=tr[now].fa=last; 88 tr[now].in=++dfsn; 89 tr[now].deep=tr[last].deep+1; 90 add(1,tr[now].in,tr[now].in,tr[now].deep);//在線段樹中更新 91 for(int i=head[now];i;i=e[i].next) 92 if(e[i].v!=last)dfs(e[i].v,now);//記得判父節點 93 tr[now].out=dfsn; 94 } 95 long long query(int num,int l,int r)//線段樹區間求和 96 { 97 if(l>r)return 0; 98 if(l<=t[num].l&&t[num].r<=r)return t[num].sum; 99 pushdown(num);//記得下傳tag 100 long long ans=0; 101 int mid=(t[num].l+t[num].r)>>1; 102 if(l<=mid)ans+=query(num<<1,l,r); 103 if(r>mid)ans+=query(num<<1|1,l,r); 104 return ans; 105 } 106 int find(int num,int goal)//查找goal在num的哪一棵子樹內 107 { 108 for(int i=head[num];i;i=e[i].next) 109 if(e[i].v!=tr[num].p&&tr[goal].in>=tr[e[i].v].in&&tr[goal].out<=tr[e[i].v].out)return e[i].v; 110 return 0; 111 } 112 double ask(int num)//REQUEST 113 { 114 if(num==rt)return (double)query(1,1,n)/n; 115 if(tr[rt].in>=tr[num].in&&tr[rt].out<=tr[num].out)//如果rt在num的子樹內;這裏的子樹是相對於原樹而言 116 { 117 int r=find(num,rt); 118 return (double)(query(1,1,tr[r].in-1)+query(1,tr[r].out+1,n))/(n-(tr[r].out-tr[r].in+1)); 119 } 120 return (double)query(1,tr[num].in,tr[num].out)/(tr[num].out-tr[num].in+1); 121 } 122 int top(int num) 123 { 124 down(num); 125 while(tr[num].c[0])num=tr[num].c[0],down(num); 126 return num; 127 } 128 void ladd(int num,int w)//子樹整體權值加減 129 { 130 if(num==rt)add(1,1,n,w); 131 else if(tr[rt].in>=tr[num].in&&tr[rt].out<=tr[num].out) 132 { 133 int r=find(num,rt); 134 add(1,1,tr[r].in-1,w); 135 add(1,tr[r].out+1,n,w); 136 } 137 else add(1,tr[num].in,tr[num].out,w); 138 } 139 void acs(int num) 140 { 141 int r=0; 142 while(num) 143 { 144 splay(num); 145 if(tr[num].c[1])ladd(top(tr[num].c[1]),1); 146 if(r)ladd(top(r),-1); 147 tr[num].c[1]=r;r=num;num=tr[num].fa; 148 } 149 } 150 void mrt(int num){splay(num);rt=num;tr[num].rev^=1;}//據題意,換根前已經access過了 151 int main() 152 { 153 char ch[15]; 154 n=read();m=read(); 155 for(int i=1;i<n;i++)x=read(),y=read(),insert(x,y); 156 build(1,1,n);dfs(1,0); 157 while(m--) 158 { 159 scanf("%s",ch);x=read(); 160 if(ch[2]==‘Q‘)printf("%.10lf\n",ask(x)); 161 else 162 { 163 acs(x); 164 if(ch[2]==‘C‘)mrt(x); 165 } 166 } 167 return 0; 168 }View Code
【bzoj 3779】重組病毒