Graph
Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 695 Accepted Submission(s): 113Problem DescriptionP. T. Tigris is a student currently studying graph theory. One day, when he was studying hard, GS appeared around the corner shyly and came up with a problem:
Given a graph with n nodes and m undirected weighted edges, every node having one of two colors, namely black (denoted as 0) and white (denoted as 1), you’re to maintain q operations of either kind:
* Change x: Change the color of xth node. A black node should be changed into white one and vice versa.
* Asksum A B: Find the sum of weight of those edges whose two end points are in color A and B respectively. A and B can be either 0 or 1.
P. T. Tigris doesn’t know how to solve this problem, so he turns to you for help.
InputThere are several test cases.
For each test case, the first line contains two integers, n and m (1 ≤ n,m ≤ 105), where n is the number of nodes and m is the number of edges.
The second line consists of n integers, the ith of which represents the color of the ith node: 0 for black and 1 for white.
The following m lines represent edges. Each line has three integer u, v and w, indicating there is an edge of weight w (1 ≤ w ≤ 231 - 1) between u and v (u != v).
The next line contains only one integer q (1 ≤ q ≤ 105), the number of operations.
Each of the following q lines describes an operation mentioned before.
Input is terminated by EOF.
OutputFor each test case, output several lines.
The first line contains “Case X:”, where X is the test case number (starting from 1).
And then, for each “Asksum” query, output one line containing the desired answer.
Sample Input
4 30 0 0 01 2 12 3 23 4 34Asksum 0 0Change 2Asksum 0 0Asksum 0 14 30 1 0 01 2 12 3 23 4 34Asksum 0 0Change 3Asksum 0 0Asksum 0 1
Sample Output
Case 1:633Case 2:304
Source2012 Asia Chengdu Regional Contest題意:給一張無向圖,有n個點,m條邊,每個點2個狀態:0和1,每條邊有一個權值w,現在給q個操作。有2種操作,change x表示改變x點的狀態。asksum a,b表示查詢所有端點為a 和 b的邊的權值之和。
思路:這題很容易想到直接暴力解決 但資料量過大 複雜度為O(q*m) 肯定是得TLE的 q不能最佳化了 那麼只能最佳化m了 暴力的話每次都要對一個點的每條相鄰邊掃描一遍 太耗時了 於是要想一想能不能每次不遍曆所有相鄰邊呢?
重新分析一下此題:由於每個點只有0 1兩個狀態,那麼答案只有3種情況,用一個數組維護即可。即ans[0]統計邊的兩端都是0的權值和,ans[1]統計邊的兩端為1和0的權值總和,ans[2]統計邊的兩端都是1的權值和。這樣只要維護好這個數組,那麼對於查詢操作,可以O(1)的時間輸出。那麼關鍵就在change操作的維護了。
先說一個想法:如果記錄每個點與之相連的點顏色分別為0和1 的權值之和,以w0和w1來表示。那麼,該點改變時,例如:噹噹前點由0->1時,ans[0]-w[0],ans[1]+w[1],ans[2]-w[1]+w[0]; 這樣就是O(1)複雜度了,而且當前點得出結果也是正確的。但是,當處理其他點時,其他點的w0和w1
沒有給到更新,這顯然是不行的,還需要再做些文章。
說到這裡,先說個結論,對於點x,與x相連並且度數大於x的度數的點不會超過 (2m)^(1/2),這個證明很好證,可以自己去推下。如果把與x相連的點分為兩類,一類的度數大於他(該類不超過(2m)^(1/2)),另一類度數小於他。對度數小於他的整體操作,度數大於他的逐邊操作,那麼就能降低複雜度了。接下來,我採取的方法是,用 w[x][0] (與x相連的邊的顏色為0)和 w[x][1] (與x相連的邊的顏色為1)表示度數比x節點小的權值之和,至於度數比它大,則逐邊進行處理,處理時更新w[y][0]、w[y][1]
(因為相對x而言,y是度數大的點,所以可以在對度數比它大的點逐邊枚舉時也可以更新w的資訊,一舉三得,因為還降低了複雜度)。這樣複雜度就可以降到 q*(m)^1/2 了。
最後,說下這種思想為什麼是正確的。對於點x,度數大於它的是逐邊處理的,ans可以直接更新;度數小於它的,資訊則沒有更新,那麼,當處理到那些度數小的點時,也是按同樣的方法在處理,度數大於的點逐邊處理,這不就是把以前沒有更新的過程給完成了嗎?這樣,就不會出現需要使用改點時,資訊沒得到完善。總而言之,就是利用度數將點分類,然後一部分整體處理,一部分枚舉。
感想:這種降低複雜度的思想真的太妙了,值得研究!啥時候能自己想出來就牛逼了。 嘻嘻
哦 對了 這題還要考慮重邊的情況,真的好沒意思,沒辦法,現場賽的題目就是各種位置都要坑你一下才行,我對那些哪些卡在重邊上的隊真的表示惋惜。
代碼:
// Exe.Time 1750MS Exe.Memory 6104K hdu暫居第五#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#define maxn 100005using namespace std;int n,m,cxx;int color[maxn];int deg[maxn];long long w[maxn][2],ww[2];long long ans[3];char s[50];struct Node{ int l,r; long long val;} edge[maxn];int point[maxn];struct node // 圖的鄰接表{ int r,next; long long val;}side[maxn];bool cmp(const Node &x1,const Node &x2){ if(x1.l<x2.l) return true ; else if(x1.l==x2.l) return x1.r<x2.r; return false ;}void addedge(int u,int v,long long cost){ cxx++; side[cxx].r=v; side[cxx].val=cost; side[cxx].next=point[u]; point[u]=cxx;}int main(){ int i,j,l,r,q,t=0,tmp,cnt,lc,nc; long long temp; while(~scanf("%d%d",&n,&m)) { t++; for(i=1; i<=n; i++) { scanf("%d",&color[i]); } ans[0]=ans[1]=ans[2]=0; for(i=1; i<=m; i++) { scanf("%d%d%I64d",&edge[i].l,&edge[i].r,&edge[i].val); ans[color[edge[i].l]+color[edge[i].r]]+=edge[i].val; // 更新初始 ans if(edge[i].l>edge[i].r) // 保證 l<r 方便下面找重邊 { temp=edge[i].l; edge[i].l=edge[i].r; edge[i].r=temp; } } sort(edge+1,edge+m+1,cmp); cnt=1; for(i=2; i<=m; i++) // 重邊合并 { if(edge[i].l==edge[cnt].l&&edge[i].r==edge[cnt].r) { edge[cnt].val+=edge[i].val; } else { cnt++; if(i!=cnt) { edge[cnt].l=edge[i].l; edge[cnt].r=edge[i].r; edge[cnt].val=edge[i].val; } } } memset(deg,0,sizeof(deg)); for(i=1;i<=cnt;i++) // 記錄各點的度數 { deg[edge[i].l]++; deg[edge[i].r]++; } cxx=0; memset(point,0,sizeof(point)); memset(w,0,sizeof(w)); for(i=1;i<=cnt;i++) // 對度數比他大的點 建立鄰接表 { l=edge[i].l; r=edge[i].r; if(deg[l]<=deg[r]) { w[r][color[l]]+=edge[i].val; // 度數小的點沒有進鄰接表 則加入W[]數組內 addedge(l,r,edge[i].val); } else { w[l][color[r]]+=edge[i].val; addedge(r,l,edge[i].val); } } printf("Case %d:\n",t); scanf("%d",&q); while(q--) { scanf("%s",s); if(s[0]=='A') { scanf("%d%d",&l,&r); printf("%I64d\n",ans[l+r]); } else { scanf("%d",&r); lc=color[r]; color[r]=!lc; ww[0]=ww[1]=0; for(i=point[r]; i ;i=side[i].next) // 度數大的點進行處理 { ww[color[side[i].r]]+=side[i].val; if(lc) // 對度數大的而言 r就是度數小的 需更新資訊 { w[side[i].r][0]+=side[i].val; w[side[i].r][1]-=side[i].val; } else { w[side[i].r][0]-=side[i].val; w[side[i].r][1]+=side[i].val; } } if(lc) // 一起更新ans { ans[0]=ans[0]+ww[0]+w[r][0]; ans[1]=ans[1]-ww[0]+ww[1]-w[r][0]+w[r][1]; ans[2]=ans[2]-ww[1]-w[r][1]; } else { ans[0]=ans[0]-ww[0]-w[r][0]; ans[1]=ans[1]+ww[0]-ww[1]+w[r][0]-w[r][1]; ans[2]=ans[2]+ww[1]+w[r][1]; } } } } return 0;}