1. 程式人生 > >解題:CEOI 2017 Mousetrap

解題:CEOI 2017 Mousetrap

題外話:

這是制杖yd的交流題目

題面

首先把捕鼠夾所在的點提出來當根,然後這變成了一棵有根樹,我們先來看耗子移動的影響

可以發現耗子往下走就回不來了,而且最後還會被困在一個葉子上,那麼這個時候我們把那個子樹到根的路徑砍成一條鏈(顯然不砍成鏈耗子可以半路跑進岔路里,至少要你再清理一次,肯定不如砍了優)再把耗子放出來就可以了。而耗子往上走我們是管不了的(指不能阻止它往上走,但是可以砍旁邊的分叉,一會具體說),畢竟我們不能把樹砍斷了,那耗子就到不了夾子了

那麼耗子的決策就明確了,它往上走一段之後或者直接找到一個子樹鑽進去,這個子樹應該是我們把子樹的根到樹的根砍成鏈需要操作次數最多的,而我們和耗子的博弈就是儘量阻止它鑽進需要次數多的子樹裡。現在考慮求耗子鑽進一個點的子樹之後我們的最少運算元:先定義$intr[i]$表示耗子鑽進$i$我們砍完邊再把它放回$i$所需的最小次數,這樣方便轉移,轉移的話因為我們每次只能砍一條邊,就是一個點兒子裡$intr$的不嚴格次大值(最大值被我們砍了,如果沒有次大值就是零)加上這個點的度數(這個點也需要砍光)再減$1$(這個點和父親之間的邊)。

注意這時的狀態還不是到根的答案,要求到根的答案我們還要處理一個$road[i]$表示從$i$到根路徑上的岔路。這裡我們把耗子所在的初始點到根的鏈拎出來,然後掃一遍就可以求出來$road$,這樣每個點到根的時間就是$intr[i]+road[fa[i]]+(fa[i]==start)$,$+(fa[i]==start)$是因為初始這個點再往下走相當於在子樹裡面又走了一步,還要把這步也擦掉 。然後我們發現直接求並不好求,因為並不容易知道耗子到底要怎麼跑,但是我們可以二分一個答案,然後檢驗耗子能不能跑進一個超過當前時間的子樹即可

 1 #include<cstdio>
 2
#include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=1000005; 6 int p[N],noww[2*N],goal[2*N],path[N],deg[N]; 7 int intr[N],road[N],tort[N],far[N],dep[N]; 8 //intr(into-tree):進入這個點的子樹又回來的最小次數 9 //road:這個點到根路徑上的岔路的數目 10 //tort(to-root):這個點到根的最小操作次數 11 int n,m,l,r,t1,t2,trp,rat,cnt,mid,ans,ops;
12 void Link(int f,int t) 13 { 14 noww[++cnt]=p[f],p[f]=cnt; 15 goal[cnt]=t,deg[t]++; 16 } 17 void DFS(int nde,int fth,int dth) 18 { 19 int max1=0,max2=0,tmp=-1; 20 far[nde]=fth,dep[nde]=dth; 21 for(int i=p[nde];i;i=noww[i]) 22 if(goal[i]!=fth) 23 { 24 DFS(goal[i],nde,dth+1),tmp=intr[goal[i]]; 25 if(tmp>=max1) max2=max1,max1=intr[goal[i]]; 26 else if(tmp>max2) max2=tmp; 27 } 28 if(~tmp) intr[nde]=max2+deg[nde]-1;//次小值更新intr,葉子節點的是0 29 if(nde==rat) 30 { 31 while(far[nde]) 32 path[++m]=nde,nde=far[nde];//把耗子初始位置到根的鏈拎出來 33 for(int i=m,lst=0;i;i--,lst=nde) 34 nde=path[i],road[nde]=road[lst]+deg[nde]-2;//掃一遍把岔路數量求出來 35 } 36 } 37 bool check(int x) 38 { 39 int lst=0; 40 for(int i=1;i<=m;i++) 41 { 42 int nde=path[i],tmp=0; 43 for(int j=p[nde];j;j=noww[j]) 44 if(goal[j]!=far[nde]&&goal[j]!=lst) 45 tmp+=(road[nde]+intr[goal[j]]+(i==1)>x); 46 //這裡+(i==1)是說初始這個點再往下走相當於在子樹裡面又走了一步,還要把這步也擦掉 47 x-=tmp,ops+=tmp,lst=nde; 48 if(x<0||ops>dep[rat]-dep[nde]+1) return false;//注意透支次數也是不行的 49 } 50 return true; 51 } 52 int main() 53 { 54 scanf("%d%d%d",&n,&trp,&rat); 55 for(int i=1;i<n;i++) 56 { 57 scanf("%d%d",&t1,&t2); 58 Link(t1,t2),Link(t2,t1); 59 } 60 DFS(trp,0,1),l=0,r=1000000; 61 while(l<=r) 62 { 63 mid=(l+r)/2,ops=0; 64 if(check(mid)) r=mid-1,ans=mid; 65 else l=mid+1; 66 } 67 printf("%d",ans); 68 return 0; 69 }
View Code