題目描述
輸入
輸出
樣例輸入

2 2 3 1 2 0 0 1 1 2 1 1 0 2 4 11 1 2 1 2 3 1 3 4 0 0 1 0 2 0 3 0 4 1 2 1 0 0 1 0 2 0 3 1 3 4 1 0 3 0 4
樣例輸出

Boys win! Girls win! Girls win! Boys win! Girls win! Boys win! Boys win! Girls win! Girls win! Boys win! Girls win!
分析
這道題我們首先想到的就是模擬,但是40000的數據顯然是太大了,肯定會超時
那么我們來模擬一下第一個樣例
這是剛開始建好的邊,建完邊后我們發現這棵樹沒有能夠修改的節點
所以我們對於第一個詢問0 1顯然要輸出 Boys win!
接下來是一個修改邊的的操作 1 2 1 1
修改完后就變成了下面這樣
接下來又是一個詢問操作0 2
我們發現在girls把(1,2)的邊權修改為0后,boys不能再進行操作
所以很顯然 Girls win!
第一個樣例我們的模擬就結束了
是不是什么規律也沒有看出來的,沒有關系,我們再來第二組
(提示:注意觀察與根節點相鄰的邊)
首先上來的就是四個詢問,分別是1、2、3、4節點作為根節點
當1作為根節點時,操作如下圖
我們發現,與根節點相鄰的邊的權值一開始為1,經過一次操作后變成了0,這時操作結束 Girls win!
當2為根節點時
我們發現,與根節點相鄰的邊有兩個,權值一開始都為1,經過兩次次操作后變成了0,這時操作結束 Boys win!
當3為根節點時
我們發現,與根節點相鄰的邊有兩個,一個為1,一個為0,經過一次操作后1的那個變成了0,這時操作結束 Girls win!
當4為根節點時(畫圖好難用)
我們發現,與根節點相鄰的邊的權值一開始為0,經過兩次操作后從0變為1又變為0,這時操作結束 Boys win!
下面是一個修改邊權的操作 1 2 1 0
修改完后,就成了這樣
當1為根節點時
我們發現,與根節點相鄰的邊的權值一開始為0,經過兩次操作后從0變為1又變為0,這時操作結束 Boys win!
當2為根節點時
我們發現,與根節點相鄰的邊有兩個,一個為1,一個為0,經過一次操作后1的那個變成了0,這時操作結束 Girls win!
當3為根節點時
我們發現,與根節點相鄰的邊有兩個,一個為1,一個為0,經過一次操作后1的那個變成了0,這時操作結束 Girls win!
最后又是一個修改邊權的操作,我們就不再模擬
通過以上的模擬,我們可以發現什么呢?
1、操作奇數次,girls win,操作偶數次boys win(是不是很顯然)
2、如果根節點只有一條邊相連,那么如果這條邊的邊權為1,需要操作奇數次才能把它變成0,因為你的每一次操作都會對它產生影響,而且你無論后面操作多少次,最終還是要把它變為0,根據第一條性質,girls win
如果邊權是0呢,就和上面相反,boys win
3、如果有多條邊呢,我們就把每一條邊上的操作次數累加,再根據性質1判斷
方法一
聽到這里,你是不是很激動呢,當給出一個根節點時,我們只需要把與它相鄰的邊的邊權加和,再判斷奇偶性就可以了
這里要注意的是,修改邊的操作不一定修改成與原來相反的價值,有可能原來價值為1,修改后還為1
代碼

1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<iostream> 5 #include<cmath> 6 #include<ctime> 7 using namespace std; 8 const int maxn=80010; 9 struct asd{ 10 int from,to,next,val; 11 }b[maxn]; 12 int head[maxn],tot=2; 13 void ad(int aa,int bb,int cc){ 14 b[tot].from=aa; 15 b[tot].to=bb; 16 b[tot].next=head[aa]; 17 b[tot].val=cc; 18 head[aa]=tot++; 19 } 20 int du[maxn]; 21 int main(){ 22 int t; 23 scanf("%d",&t); 24 while(t--){ 25 memset(head,-1,sizeof(head)); 26 memset(&b,0,sizeof(struct asd)); 27 memset(du,0,sizeof(du)); 28 tot=2; 29 int n,m; 30 scanf("%d%d",&n,&m); 31 for(int i=1;i<n;i++){ 32 int aa,bb,cc; 33 scanf("%d%d%d",&aa,&bb,&cc); 34 ad(aa,bb,cc); 35 ad(bb,aa,cc); 36 du[aa]+=cc; 37 du[bb]+=cc; 38 } 39 while(m--){ 40 int cc; 41 scanf("%d",&cc); 42 if(cc==0){ 43 int aa; 44 scanf("%d",&aa); 45 int ans=du[aa]; 46 if(ans%2==0) printf("Boys win!\n"); 47 else printf("Girls win!\n"); 48 } else { 49 int aa,bb,cc; 50 scanf("%d%d%d",&aa,&bb,&cc); 51 for(int i=head[aa];i!=-1;i=b[i].next){ 52 int u=b[i].to; 53 if(bb==u && b[i].val!=cc){ 54 b[i].val=cc; 55 b[i^1].val=cc; 56 if(cc==1){ 57 du[aa]++; 58 du[bb]++; 59 } else { 60 du[aa]--; 61 du[bb]--; 62 } 63 break; 64 } 65 if(bb==u) break; 66 } 67 } 68 } 69 } 70 return 0; 71 }
寫完后,我們把它交上去,發現過了,時間消耗還不多
但是我們細細一想會發現,這種做法的時間效率不能保證,我們完全可以造一組數據將它卡成n^2
比如下面這樣
m,n小於40000,我們完全可以按照上面那樣建邊,然后來39999次修改操作
最后再來一次詢問
而且題目中最多會給出5組數據
那么耗時就是5*40000*40000,顯然會T(后面會有樣例,大家可以試一下)
方法二
既然如此,那我們就要考慮怎么省去遍歷邊的操作
題目中只給出了0,1兩種值
所以,聯系我們最近學過的內容
沒錯,就是bitset
代碼

1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<iostream> 5 #include<cmath> 6 #include<bitset> 7 #include<ctime> 8 using namespace std; 9 bitset<53005> bit[53005]; 10 int du[53000]; 11 int main(){ 12 int t; 13 scanf("%d",&t); 14 while(t--){ 15 memset(du,0,sizeof(du)); 16 for(int i=0;i<50000;i++){ 17 bit[i].reset(); 18 } 19 int n,m; 20 scanf("%d%d",&n,&m); 21 for(int i=1;i<n;i++){ 22 int aa,bb,cc; 23 scanf("%d%d%d",&aa,&bb,&cc); 24 if(cc==1) bit[aa][bb]=bit[bb][aa]=1; 25 du[aa]+=cc,du[bb]+=cc; 26 } 27 while(m--){ 28 int cc; 29 scanf("%d",&cc); 30 if(cc==0){ 31 int aa; 32 scanf("%d",&aa); 33 int ans=du[aa]; 34 if(ans%2==0) printf("Boys win!\n"); 35 else printf("Girls win!\n"); 36 } else { 37 int aa,bb,cc; 38 scanf("%d%d%d",&aa,&bb,&cc); 39 if(bit[aa][bb]!=cc){ 40 if(cc==1){ 41 bit[aa][bb]=bit[bb][aa]=1; 42 du[aa]++,du[bb]++; 43 } else { 44 bit[aa][bb]=bit[bb][aa]=0; 45 du[aa]--,du[bb]--; 46 } 47 } 48 } 49 } 50 } 51 return 0; 52 }
但是很遺憾內存開不下
雖然bitset很優秀,只占一個二進制位,但是題目中的內存限制為65536 kB
最多可以開一維的bitset數組65536*1024*8=536870912(5億多,是不是很強大)
但因為是二維數組,我們開方后就只有23000了,只能達到原題數據的一半左右
如果我們開40000*40000顯然會M掉
如果開23000*23000呢,會RE,因為下標訪問bitset數組並不會檢查越界
而且因為數組過大,你不可能每組數據都重新開一個bitset數組,所以你要初始化,但初始化就要花費幾百毫秒
方法三
這時,優秀的解法該出現了
是什么呢?
答案就是map+pair
map的用法大家應該都很熟悉了,我們就簡單講一下pair吧
摘自百度百科:
定義:c++中的結構模板,定義在頭文件<utility>中,提供一個包含2個數據成員的結構體模板。繼承與_Pair_base結構體模板。通過first,second訪問2個成員,有 operator= 和 swap 方法。
以下內容摘自:https://blog.csdn.net/qq_42232118/article/details/82078854
其實,這里pair的作用就是把兩個元素整合在一起
那么這個算法優秀在哪里呢?
map查詢元素的復雜度為O(logn),而枚舉的話復雜度是隨機的,幸運的話,你一次就可以查詢完,但是遇上特殊情況的話,你會被卡掉
代碼

1 #include<cstdio> 2 #include<cstring> 3 #include<map> 4 #include<utility> 5 #include<ctime> 6 using namespace std; 7 int deg[40005]; 8 map<pair<int,int>,int> amap; 9 int main(){ 10 int t,n,m,x,y,z,id,j; 11 scanf("%d",&t); 12 while(t--){ 13 memset(deg,0,sizeof(deg)); 14 amap.clear(); 15 scanf("%d%d",&n,&m); 16 for(int i=1;i<n;i++){ 17 scanf("%d%d%d",&x,&y,&z); 18 if(x>y){int te=x;x=y;y=te;} //統一順序這樣方便后期查找 19 amap[make_pair(x,y)]=z; 20 if(z==1){ 21 deg[x]++; 22 deg[y]++; 23 } 24 } 25 for(int i=0;i<m;i++){ 26 scanf("%d",&id); 27 if(id==0){ 28 scanf("%d",&x); 29 if(deg[x]%2) printf("Girls win!\n"); 30 else printf("Boys win!\n"); 31 } 32 else{ 33 scanf("%d%d%d",&x,&y,&z); 34 if(x>y){int te=x;x=y;y=te;} 35 if(amap[make_pair(x,y)]!=z){ 36 if(z==0){ 37 amap[make_pair(x,y)]=0; 38 deg[x]--; 39 deg[y]--; 40 } 41 else{ 42 amap[make_pair(x,y)]=1; 43 deg[x]++; 44 deg[y]++; 45 } 46 } 47 } 48 } 49 } 50 return 0; 51 }
比較
這是一組符合題目要求的極端樣例
樣例太大,插不上,就放一個生成數據的代碼吧

1 #include<bits/stdc++.h> 2 using namespace std; 3 int main(){ 4 freopen("data.in","w",stdout); 5 srand(time(NULL)); 6 printf("5\n"); 7 for(int i=1;i<=5;i++){ 8 printf("39999\n39999\n"); 9 for(int i=2;i<=39999;i++){ 10 printf("1 %d %d\n",i,i%2); 11 } 12 for(int i=1;i<=39998;i++){ 13 printf("1 1 2 0\n"); 14 } 15 printf("0 1\n"); 16 } 17 return 0; 18 }
在自己電腦上測的話畢竟不太准,那么我們可以借助一個很好的平台——洛谷
我在洛谷創建了一個題目,數據用的是極端數據(就是用上面的代碼生成的數據)
(這里為了關照一下bitset,我把內存限制開大了,為了更清晰的比較,我把時間限制調到了10s,而且都沒有開O2優化)
大家可以拿自己的代碼試一下,看一下會不會T掉
這是我的測試結果
map
第1組數據是最極限的那一種,2到5組數據也會卡枚舉的方法,但沒那么嚴重,6到10是小數據
我們發現map的效率比較穩定,取決於n的大小,是一種不錯的方法
枚舉
枚舉的話,卡枚舉的前5組都超過了2秒,即使時間開到了10s,最極限的第一組也會T掉,但是隨機數據還是很快的
bitset
biset初始化會占用大量時間,不划算,小數據也會跑到幾百毫秒
而且內存開到336.82MB也不現實,所以還是用map吧
總結:如果數據隨機的話我覺得差別不大,map每次查詢是log(n),不管數據如何都比較穩定,直接爆搜隨機數據還可以,但極限數據或特別的構圖可能會超時
biset的話內存是個短板