HDU-5963 朋友 思維
- 2020 年 4 月 11 日
- 筆記
題目鏈接http://acm.hdu.edu.cn/showproblem.php?pid=5963
吐槽
這道題我第一眼看,嗯??博弈論?還是樹上的?我好像不會啊。。。但是一想某人的話,感覺這個應該也不會太難,可能有規律
分析
於是我就從樣例開始仔細思考找規律,第一個樣例應該是看不出來啥,但第二個內容量就比較豐富了。但我模擬完樣例二依舊沒發現什麼,難道這道題真要建個線段樹什麼的??接著我把關注點放到了輸出上,輸出只有兩種,那是不是應該存在某種奇偶關係?還是要先考慮鏈的情況,因為從無根樹上的一個點出發遍歷就相當於走幾條鏈(好像樹的問題大部分都跟鏈有關)。下面模擬一下
首先要注意到題目中的暗示,每次都要找一個到父親節點權值為1的點,這就說明了這個問題的限制,它總會結束。先不考慮修改,如果以1為根,不難看出女孩會贏,其中一種走法是
當然也有別的走法,但總會是女生贏。
如果以2為根呢?不難得出還是女孩贏,其中一種走法為
那麼是不是圖中邊權為1的邊數為偶數時,就是女生贏呢?仍舊以2為根,我們在4後邊再跟一個節點。
會發現這樣還是女生贏。
那在根節點2後邊跟這個節點呢?
這時候再去模擬就會發現是男生贏了。所以我們猜測,讓女生贏的並不是圖中邊權為1的邊數,而是根節點周圍邊權為1的邊數,根據這個大膽的猜測,我寫出了下面的程式碼,好像還白寫了一個加邊函數。如果你去用樣例測試,發現是對的,所以我興奮的交了上去,WA
#include<cstdio> #include<cstring> using namespace std; const int N=4e4+10; struct Edge{ int to,nxt,val; }e[N<<1]; int Head[N],len; void Ins(int a,int b,int c){ e[++len].to=b;e[len].val=c; e[len].nxt=Head[a];Head[a]=len; } int cnt[N]; int main(){ int t; scanf("%d",&t); while(t--){ int n,m; scanf("%d%d",&n,&m); memset(cnt,0,sizeof(cnt)); for(int i=1;i<n;i++){ int a,b,c; scanf("%d%d%d",&a,&b,&c); cnt[a]+=c; cnt[b]+=c; } for(int i=1;i<=m;i++){ int op,x,y,z; scanf("%d",&op); if(op==1){ scanf("%d%d%d",&x,&y,&z); if(z==1)cnt[x]++,cnt[y]++; else cnt[x]--,cnt[y]--; } else { scanf("%d",&x); if(cnt&1)printf("Girls win!n"); else printf("Boys win!n"); } } } }
二次分析
既然這個程式碼過了樣例,就說明它應該不是偶然,所以應該是我少考慮了什麼。再回去讀了一邊題,發現題中並沒有說當(op==1)時,變換的權值與原來的權值相等,也就是說如果我每次都把1變為0,0變為1,那麼我上邊的程式碼是可以的,也就是樣例情況,但問題就出在它可能是1變為1,0變為0,所以每次必須掃描一邊根周圍的邊權之和,即下邊的程式碼。
#include<cstdio> #include<cstring> using namespace std; const int N=4e4+10; struct Edge{ int to,nxt,val; }e[N<<1]; int Head[N],len; void Ins(int a,int b,int c){ e[++len].to=b;e[len].val=c; e[len].nxt=Head[a];Head[a]=len; } int main(){ int t; scanf("%d",&t); while(t--){ int n,m; scanf("%d%d",&n,&m); memset(Head,0,sizeof(Head)); len=0; for(int i=1;i<n;i++){ int a,b,c; scanf("%d%d%d",&a,&b,&c); Ins(a,b,c);Ins(b,a,c); } for(int i=1;i<=m;i++){ int op,x,y,z; scanf("%d",&op); if(op==1){ scanf("%d%d%d",&x,&y,&z); for(int i=Head[x];i;i=e[i].nxt) if(e[i].to==y)e[i].val=z; for(int i=Head[y];i;i=e[i].nxt) if(e[i].to==x)e[i].val=z; } else { int cnt=0; scanf("%d",&x); for(int i=Head[x];i;i=e[i].nxt)cnt+=e[i].val; if(cnt&1)printf("Girls win!n"); else printf("Boys win!n"); } } } }
證明
寫完之後感覺就這麼過去的話不是很好,萬一猜錯了就很尷尬,所以想一下證明。
為了簡化問題,我們只把鏈的情況證明了就好。如果連接根節點的邊權值為1,其餘邊有兩種情況,一是全為1的,二是含0的,分開討論一下。
如果含有0,
因為是女生先任意選點,所以讓女生選擇最後一個點,這樣連接根節點的那條邊就會被置為0,而因為含有0,所以肯定還會剩下邊權為1的,這時由於男生不得不去變換邊,所以一定會去變換權值為1的,由於根節點在男生變換的時候為0,所以這樣的話只有可能是女生讓根節點變為0,也就是只有女生會贏。
如果不含0都是1呢?那女生就可以一次性讓男生沒的變換,仍然是女生贏。
當連接根節點的邊權值為0時就會反過來,證畢。