樹形DP入門學習


這里是學習韋神的6道入門樹形dp進行入門,本來應放在day12&&13里,但感覺這個應該單獨放出來好點。

這里大部分題目都是參考的韋神的思想。

A - Anniversary party

題意:
一個樹,每個點有一個“快樂”值,父子結點不能同時快樂,問這個結構的最大快樂值。

Thinking:

思考如何寫出樹規方程,即思考根與子節點的關系。

dp[i][0]:表示不邀請i員工其子樹達到的最大快樂值,dp[i][1]則表示邀請。

這時根與子節點的關系就顯然了。

 

 1 #include <cstdio>
 2 #include <iostream>
 3 #include <cstring>
 4 #include <algorithm>
 5 #include <vector>
 6 using namespace std;  7 typedef long long LL;  8 #define mst(s, t) memset(s, t, sizeof(s))
 9 const int INF = 0x3f3f3f3f; 10 const int maxn = 6010; 11 vector<int> G[maxn]; 12 int father[maxn], dp[maxn][2]; 13 void dfs(int root){ 14     for(int i=0; i<G[root].size(); i++){ 15  dfs(G[root][i]); 16  } 17     for(int i=0; i<G[root].size(); i++){ 18         dp[root][0] += max(dp[G[root][i]][0], dp[G[root][i]][1]); 19         dp[root][1] += dp[G[root][i]][0]; 20  } 21 } 22 int main() 23 { 24     freopen("in.txt", "r", stdin); 25     mst(dp, 0); mst(father, -1); 26     int n; 27     scanf("%d", &n); 28     for(int i=1; i<=n; i++){ 29         scanf("%d", &dp[i][1]); 30  G[i].clear(); 31  } 32     int fa, so; 33     while(scanf("%d%d", &so, &fa) && fa && so){ 34  G[fa].push_back(so); 35         father[so] = fa; 36  } 37     int root = 1; 38     while(father[root] != -1)    root=father[root]; 39  dfs(root); 40     printf("%d\n", max(dp[root][0], dp[root][1])); 41     return 0; 42 }
View Code

 

B - Strategic game

題意:

現在要在一棵樹上布置士兵,每個士兵在結點上,每個士兵可以守護其結點直接相連的全部邊,問最少需要布置多少個士兵。

這題解法與上題相似。

1 dp[root][0] += dp[G[root][i]][1]; 2 dp[root][1] += min(dp[G[root][i]][0], dp[G[root][i]][1]);

在讀題時想了下結點A的父節點B的變化會影響到A和B的父節點C,會影響到總人數,后來又想了想,這不就是dp要解決的問題呀,在每個階段做一個決策,以求達到預定的效果。

 1 #include <cstdio>
 2 #include <iostream>
 3 #include <cstring>
 4 #include <algorithm>
 5 #include <vector>
 6 using namespace std;  7 typedef long long LL;  8 #define mst(s, t) memset(s, t, sizeof(s))
 9 const int INF = 0x3f3f3f3f; 10 const int maxn = 1510; 11 int dp[maxn][2], father[maxn]; 12 vector<int> G[maxn]; 13 void dfs(int root){ 14     for(int i=0; i<G[root].size(); i++){ 15  dfs(G[root][i]); 16  } 17     for(int i=0; i<G[root].size(); i++){ 18         dp[root][0] += dp[G[root][i]][1]; 19         dp[root][1] += min(dp[G[root][i]][0], dp[G[root][i]][1]); 20  } 21 } 22 int main() 23 { 24     //freopen("in.txt", "r", stdin);
25     int n; 26     while( scanf("%d", &n) != EOF){ 27         for(int i=0; i<=n; i++){ 28  G[i].clear(); 29             dp[i][1] = 1,  dp[i][0] = 0; 30             father[i] = -1; 31  } 32         for(int i=0; i<n; i++){ 33             int root, node, cnt; 34             scanf("%d:(%d)",&root, &cnt); 35             for(int i=0; i<cnt; i++){ 36                 scanf("%d", &node); 37  G[root].push_back(node); 38                 father[node] = root; 39  } 40  } 41         int root = 1; 42         while(father[root] != -1) root=father[root]; 43  dfs(root); 44         printf("%d\n", min(dp[root][0], dp[root][1])); 45  } 46     return 0; 47 }
View Code

 

C - Tree Cutting 

題意:

一棵無向樹,結點為n(<=10,000),刪除哪些結點可以使得新圖中每一棵樹結點小於n/2。

Thinking:

真的是菜的無語,面對不會寫的題總有懶於思考的毛病。

下面記錄解決此題的心得:這題給我一種搜索而非dp的感覺,可能有什么我沒發現的深意吧。

在遍歷樹的過程中,訪問每個node,維護兩個值:

  1. 所有子樹的結點數的最大值childmax
  2. 所有子樹(這里包括node)的結點數之和sum。

遞歸過程中用上一層的sum,不斷更新這一層的childmax。

而childmax和sum則共同用來判斷這個node是否可以刪除。

下面再分析判斷條件: childmax<=n/2 && n-sum<=n/2

childmax<=n/2 :去掉node后,原先node的子樹均滿足條件。

n-sum<=n/2  :去掉node后,原先除node和node的所有子樹外的樹(就當是node的祖先樹吧)均滿足條件。

 

 1 #include <cstdio>
 2 #include <iostream>
 3 #include <cstring>
 4 #include <algorithm>
 5 #include <vector>
 6 using namespace std;  7 typedef long long LL;  8 #define mst(s, t) memset(s, t, sizeof(s))
 9 const int INF = 0x3f3f3f3f; 10 const int maxn = 10010; 11 vector<int> G[maxn]; 12 int ans[maxn], num, n; 13 int dfs(int node, int father){ 14     int sum = 1, childmax = 0;   //若是葉子結點則return sum=1,否則求其子樹(包括自己)的總結點數
15     for(int i=0; i<G[node].size(); i++){ 16         if(G[node][i] == father)continue; //因為是樹結構,這里可以在無向時避免遍歷成環 
17         int sum_son = dfs(G[node][i], node); 18         childmax = max(sum_son, childmax);//所有子樹的結點數的最大值
19         sum += sum_son;//sum:node的子樹的結點數和
20  } 21     childmax = max(childmax, n-sum); 22     if(childmax <= n/2){ 23         /*
24  * 當node結點的孩子結點的結點數最大為Sum,若Sum<=n/2,則該點符合條件 25  * 因為去掉node后,任意子樹結點數<=n/2, max()保證其非子樹結點和仍<=n/2 26  * 故該點滿足條件 27         */
28         ans[num++] = node; 29  } 30     return sum; 31 } 32 int main() 33 { 34     //freopen("in.txt", "r", stdin);
35     scanf("%d", &n); 36     for(int i=0; i<n-1; i++){ 37         int a, b; 38         scanf("%d%d", &a, &b); 39  G[a].push_back(b); 40  G[b].push_back(a); 41  } 42     num = 0; 43     int tmp = dfs(1, 0); 44     //cout << n << "==" << tmp << endl; //驗證
45     sort(ans, ans+num); 46     if(num){ 47         for(int i=0; i<num; i++){ 48             printf("%d\n", ans[i]); 49  } 50     }else{ 51         printf("NONE\n"); 52  } 53     return 0; 54 }
View Code

 

 

 

D - Tree of Tree

題意:一棵結點帶權樹,大小(結點數)為k的子樹的權值和最大為多少。

這道題促使我寫這篇學習心得,感覺稍微需要點思考的dp題我連思路都看得費勁。博客里的思路真的是想了好久,又找了份前輩的AC代碼敲了敲(敲出來竟然連樣例都沒過,哎),趁熱記錄下自己的划水心得。

開始是想到要用狀態dp[i]][j]表示node i的 結點數為j的子樹 的最大權值和。 但是如何動態規划卻沒有思路。

這里對每個子節點進行背包dp, dp[j] = max(dp[j], dp[j-w[i]]+v[i]) ,從后往前dp是因為若從后往前會使v的某一個t被重復選取。

這道題整體思路還不清晰,要再多看看。

 1 #include <cstdio>
 2 #include <iostream>
 3 #include <cstring>
 4 #include <algorithm>
 5 #include <vector>
 6 using namespace std;  7 typedef long long LL;  8 #define mst(s, t) memset(s, t, sizeof(s))
 9 const int INF = 0x3f3f3f3f; 10 const int maxn = 110; 11 vector<int> G[maxn]; 12 int dp[maxn][maxn];  //dp[i][j]:node[i]結點數為j的子樹的最大權值
13 int k, ans, cnt[maxn], weight[maxn]; 14 int dfs(int node, int father){ 15     cnt[node] = 1; 16     for(int i=0; i<G[node].size(); i++){ 17         if(G[node][i] == father) continue; 18         cnt[node] += dfs(G[node][i], node); 19  } 20     dp[node][1] = weight[node]; 21     //這里初始化不能在main()內 ?? 
22 /*
23  * dp[node][j-t]是之前的子節點為根更新的子樹產生的 24  * dp[v][t]是以當前子節點為根的子樹產生的 25  * j如果順序遍歷,前面dp[node][j]的更新會影響后面的dp[node][j-t],導致后面 26  *更新dp[node][j]時是一當前子節點為根的子樹產生的 27 */
28     for(int i = 0; i < G[node].size(); i++){ 29         int v = G[node][i]; 30         for(int j = cnt[node]; j >= 1; j--){ 31             for(int t = 0; t<j && t<=cnt[v]; t++){ 32                 dp[node][j] = max(dp[node][j], dp[node][j-t]+dp[v][t]); 33  } 34  } 35  } 36     ans = max(ans, dp[node][k]); 37     return cnt[node]; 38 } 39 int main() 40 { 41     freopen("in.txt", "r", stdin); 42     int n; 43     while(scanf("%d%d",&n, &k) != EOF){ 44         mst(dp, 0);  ans = 0; 45         for(int i=0; i<maxn; i++){ 46  G[i].clear(); 47  } 48         for(int i=0; i<n; i++){ 49             scanf("%d", &weight[i]); 50  } 51         int a, b; 52         for(int i = 1; i < n; i++){ 53             scanf("%d%d", &a, &b); 54  G[a].push_back(b); 55  G[b].push_back(a); 56  } 57         dfs(0, -1); 58         printf("%d\n", ans); 59  } 60     return 0; 61 }

 

 

E - Cell Phone Network

題意:給n[1,10000]個點,n-1條邊,樹形結構,從n個點中取盡量少的點構成一個集合,使剩下所有點都能與這個集合中的部分點相連。

(這個概念叫最小支配集)

dp[u][]:以點u為根的被染色的點的個數

dp[u][0]:u不染色,父節點染色覆蓋u        min(1, 2)u不染色,不能覆蓋子節點v,故要不v染色覆蓋自己,要不v被v染色的子節點覆蓋

dp[u][1]:u不染色,子節點存在被染色的覆蓋u                  min(1,2)u不染色,所以子節點v不存在被染色的父親;若所有v均不染色,此時u未被覆蓋,故需要有一個v來染色,選擇min(dp[v][2]-dp[v][1])即可。

dp[u][2]:u染色                      min(0,1,2)+1 子節點v染不染色都可以;自己染色故需+1          

 1 /*
 2  * poj3659  3  * 最小支配集:從所有頂點中取盡量少的點組成一個集合,  4  * 使剩下的所有點都與取出來的所有點相連。  5  * dp[u]:以點u為根的被染色的點的個數  6  *  7  * dp[u][0]:u不染色,父節點染色覆蓋u  8  * dp[u][1]:u不染色,子節點存在被染色的覆蓋u  9  * dp[u][2]:u染色 10 */
11 #include <iostream>
12 #include <cstdio>
13 #include <cstring>
14 #include <vector>
15 using namespace std; 16 const int inf = 0x3f3f3f3f; 17 const int maxn = 10010; 18 vector<int> G[maxn]; 19 int dp[maxn][3], n; 20 
21 void dfs(int u, int f){ 22     //葉子結點
23     if(G[u].size()==1 && G[u][0]==f){ 24         dp[u][0] = 0; 25         dp[u][1] = inf; 26         dp[u][2] = 1; 27         return; 28  } 29     int mini = inf, flag = 1; 30     for(int i=0; i<G[u].size(); i++){ 31         int v = G[u][i]; 32         if(v == f) continue; 33  dfs(v, u); 34         dp[u][0] += min(dp[v][1], dp[v][2]); 35         dp[u][2] += min(min(dp[v][0], dp[v][1]), dp[v][2]); 36         if(dp[v][1] < dp[v][2]){ 37             dp[u][1] += dp[v][1]; 38             mini = min(mini, (dp[v][2] - dp[v][1])); 39         }else{ 40             flag = 0; 41             dp[u][1] += dp[v][2]; 42  } 43 
44  } 45     dp[u][2]++;   //u點需要染色 
46     if(flag){ 47 /*
48  * 如果所有子節點dp[v][1]<dp[v][2],則所有子節點點不放,這時必須有一個孩子結點放才可以保證 49  * u被覆蓋 50 */ 
51         dp[u][1] += mini; 52  } 53 } 54 
55 int main(){ 56     //freopen("in.txt", "r", stdin);
57     int n; scanf("%d", &n); 58     for(int i=0; i<=n; i++)G[i].clear(); 59     for(int i=1; i<n; i++){ 60         int a, b; scanf("%d%d", &a, &b); 61  G[a].push_back(b); G[b].push_back(a); 62  } 63     dfs(1, -1); 64     printf("%d\n", min(dp[1][1], dp[1][2])); 65     //1是根,無父節點 
66     return 0; 67 }
View Code

 

 

F - Computer

題意:一棵邊帶權值的樹,求每個點在樹上的最遠距離。

參考blog

1、dp:計算點v在樹上的最遠距離,通過dfs()尋找。v通過v的子樹可以找到最遠距離,v也可以通過v的父節點找到最遠距離。通過子樹找,向下遍歷更新即可(即向下尋找)。通過父節點找,需要知道父節點的最遠距離,父節點可以通過找自己的父節點獲得最遠距離(即一直向上尋找),也可以通過尋找子樹獲得最遠距離(但不能包含以v為根的子樹(否則會重復))(即先向上后向下尋找),這里就需要結點的次遠距離(即該距離是不包含結點最遠距離上的第一個結點的最遠距離,故稱為次遠距離)。

 1 /*
 2  * hdu2196  3  *  4 */
 5 #include <bits/stdc++.h>
 6 #define LL long long
 7 using namespace std;  8 const int maxn =  1e4+10;  9 struct edge{ 10     int to, val; 11     edge(int a, int b) : to(a), val(b) {} 12 }; 13 vector<edge> G[maxn]; 14 int dp[3][maxn], id[maxn]; 15 //樹只有一個父親,會有多個兒子 16 //id[v]: v在v的子樹中可以得到的最大距離,所經過的第一個孩子結點 17 //dp[v][0]: v在v的所有子樹中獲得的最長距離 18 //dp[v][1]: v的孩子的第二長距離 19 //dp[v][2]: v通過父親獲得的最長距離
20 void dfs1(int x, int f){ 21     for(int i=0; i<G[x].size(); i++){ 22         int to = G[x][i].to, w = G[x][i].val; 23         if(to == f) continue; 24  dfs1(to, x); 25         if(dp[0][x] < dp[0][to] + w){ 26             dp[0][x] = dp[0][to] + w; 27             id[x] = to;   //記錄點x的最大距離經過的第一個孩子結點 
28  } 29  } 30     
31     for(int i=0; i<G[x].size(); i++){ 32         int to = G[x][i].to, w = G[x][i].val; 33         if(to == f) continue; 34         if(id[x] == to) continue;  //找次大的 
35         dp[1][x] = max(dp[1][x], w + dp[0][to]); 36  } 37 } 38 void dfs2(int x, int f){ 39     for(int i=0; i<G[x].size(); i++){ 40         int to = G[x][i].to, w = G[x][i].val; 41         if(to == f) continue; 42         if(to == id[x]){ 43             dp[2][to] = max(dp[2][x], dp[1][x]) + w; 44             //to是x的孩子:to的最大距離是 x不經過to的最大距離(即次大距離)[向下的]和 45             //x向上的最大距離 的最大值 + dist(x,to) (畫圖理解) 46             //這里的轉移也是dp[v][1]和id[x]存在的意義 
47         }else{ 48             dp[2][to] = max(dp[2][x], dp[0][x]) + w; 49             //to不是x最大距離經過的點 50             //則to的最大距離是dist(x,to)和x向上或向下的最大距離的最大值
51  } 52  dfs2(to, x); 53         //0和1子樹的信息可以直接用,2也是步步更新,一直到最優 
54  } 55 } 56 int main(){ 57     //freopen("in.txt", "r", stdin);
58     int n; 59     while(scanf("%d", &n) != EOF){ 60         memset(dp, 0, sizeof(dp)); 61         for(int i=1;i<=n;i++)G[i].clear(); 62         for(int i=2; i<=n; i++){ 63             int a, b; scanf("%d%d", &a, &b); 64  G[i].push_back(edge(a, b)); 65  G[a].push_back(edge(i, b)); 66  } 67         dfs1(1, -1); 68         dfs2(1, -1); 69         for(int i=1; i<=n; i++){ 70             printf("%d\n", max(dp[0][i], dp[2][i])); 71  } 72  } 73     return 0; 74 }
View Code

 

2、用樹的直徑求解:3次dfs()。前兩次求樹的直徑,后兩次求得所有點距離直徑端點的最遠距離。

 1 /*
 2  * 樹中的最長路徑,  3 */ 
 4 #include <bits/stdc++.h>
 5 using namespace std;  6 const int maxn = 1e4+10;  7 struct edge{  8     int to, val;  9     edge(int a, int b) : to(a), val(b) {} 10 }; 11 vector<edge> G[maxn]; 12 int dp[maxn], max_len, s; 13 
14 void dfs(int x, int f, int len){ 15 //len:起點到當前點的距離
16     if(max_len <= len){ 17         s = x; 18         max_len = len; 19  } 20     for(int i=0; i<G[x].size(); i++){ 21         int to = G[x][i].to, w = G[x][i].val; 22         if(f == to) continue; 23         dfs(to, x, len+w); 24         dp[to] = max(dp[to], len+w); 25         //更新起點到當前點的距離 
26  } 27 } 28 int main(){ 29     freopen("in.txt", "r", stdin); 30     int n; 31     while(scanf("%d", &n) != EOF){ 32         memset(dp, 0, sizeof(dp)); 33         for(int i=1;i<=n;i++) G[i].clear(); 34         for(int i=2; i<=n; i++){ 35             int a,b; scanf("%d%d",&a, &b); 36  G[i].push_back(edge(a, b)); 37  G[a].push_back(edge(i, b)); 38  } 39         s=0, max_len=0; 40         dfs(1, -1, 0); 41         dfs(s, -1, 0); 42         dfs(s, -1, 0); 43         for(int i=1; i<=n; i++){ 44             printf("%d\n", dp[i]); 45  } 46  } 47     return 0; 48 }
View Code

 

 


 

hdu6446 Tree and Permutation

題意:給一個n(1e5)個點,n-1條邊的樹,按結點進行全排列,對每個全排列,求其第一個結點到其余結點的距離之和,再求全排列的和。

每條邊單獨計算,邊E左邊x個點,右邊(n-x)個點。則在全排列n!中,每種排列有n-1段,每段的貢獻是 2*x*(n-x)*(n-2)!*w ,一共n-1段,則貢獻為 2*x*(n-x)*(n-1)!*w 。一共n-1條邊,sum()即可

 

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int maxn = 1e5+10;
 4 #define LL long long
 5 const LL mod = 1e9+7;
 6 struct edge{
 7     int to, val;
 8     edge(int a, int b) : to(a), val(b) {}
 9 };
10 vector<edge> G[maxn];
11 LL d[maxn], w[maxn], node[maxn];
12 //node[x]: f-->x這條邊在x一邊的點的個數
13 //w[x]: f-->x這條邊的權值
14 //d[i]: i! 
15 void get_d(){
16     d[1] = 1;
17     for(int i=2; i<maxn; i++){
18         d[i] = (1LL * i * d[i-1]) % mod;
19     }
20 }
21 
22 LL dfs(int x, int f){
23     LL ans = 1;
24     for(int i=0; i<G[x].size(); i++){
25         int v = G[x][i].to;
26         if(v == f){
27             w[x] = (LL)G[x][i].val;  //將邊f---->x的權值存在當前結點w[x]
28         }else{
29             ans += dfs(v, x); //統計結點數 
30         }
31     }
32     if(f!=-1 && G[x].size()==1){ //葉子結點 
33         return node[x] = 1;   //葉子結點一邊的點數為1 
34     }
35     return node[x] = ans;
36 }
37 int main(){
38     //freopen("in.txt", "r", stdin);
39     get_d();
40     int n;
41     while(scanf("%d", &n) != EOF){
42         for(int i=0; i<=n; i++) G[i].clear();
43         for(int i=1; i<n; i++){
44             int x, y, z;  scanf("%d%d%d", &x, &y, &z);
45             G[x].push_back(edge(y, z));
46             G[y].push_back(edge(x, z)); 
47         }
48         dfs(1, -1);
49         LL ans = 0;
50         //ans = sum(2*x*(n-x)*(n-1)!*w[i]) = (n-1)!*sum(2*x*(n-x)*w[i])
51         for(int i=2; i<=n; i++){
52             ans  = (ans + ( ((2*node[i]*(n-node[i]))%mod) * w[i])%mod )%mod;
53         }
54         printf("%lld\n", (ans * d[n-1])%mod);
55     }
56     return 0;
57 } 
View Code

 

UVA 10859 Placing Lampposts(訓練指南P70)

題意:n個點m條邊的無向無環圖。在盡量少的結點上放燈,使所有邊都被照亮,燈的總數最小的前提下,被兩盞燈同時照亮的邊數盡量大。

與E求最小支配集相似,多了同時照亮的邊數盡量大的目標。

下面兩個關於這題的思路很nice:

多目標優化問題這里有一個思路是 x=Ma+c,M是“c的最大理論值與a的最小理論值之差”還要大的數。

還需要進行目標轉換:邊數一定,被兩盞燈同時照亮的邊數比較大,則被一盞燈照亮的邊數盡量小。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int maxn = 1010;
 4 vector<int> G[maxn];
 5 int d[maxn][2], n, m;
 6 //d[i][j]: i的父節點是否放燈的值為j;  d[i][j]:以i為根的最小x值 
 7 bool vis[maxn][2];
 8 int dfs(int i, int j, int f){
 9     int &ans = d[i][j];
10     if(vis[i][j]) return ans;
11     vis[i][j] = true;
12     ans = 2000;   //i結點放燈,權重很大 
13     for(int k=0; k<G[i].size(); k++){
14         if(G[i][k] == f) continue;
15         ans += dfs(G[i][k], 1, i);
16     }
17     if(j==0 && f>=0){
18     //因為i的父節點沒放燈,所以這是被一盞燈照亮 
19         ans++;
20     }
21     if(j || f<0){        //i是根或i的父親放燈,i可以不放燈 
22         int sum = 0;
23         for(int k=0; k<G[i].size(); k++){
24             if(G[i][k] == f) continue;
25             sum += dfs(G[i][k], 0, i);
26         }
27         if(f >= 0) sum++;
28         ans = min(ans, sum);
29     }
30     return ans;
31 }
32  
33 int main(){
34     //freopen("in.txt", "r", stdin);
35     int t;
36     scanf("%d", &t);
37     while(t--){
38         int n, m;
39         scanf("%d%d", &n, &m);
40         for(int i=0; i<=n; i++)G[i].clear();
41         for(int i=0; i<m; i++){
42             int x, y;
43             scanf("%d%d", &x, &y);
44             G[x].push_back(y);
45             G[y].push_back(x);
46         }
47         memset(vis, 0, sizeof(vis));
48         int ans = 0;
49         for(int i=0; i<n; i++){
50             if(!vis[i][0]){
51                 ans += dfs(i, 0, -1);
52             }
53         }
54         printf("%d %d %d\n", ans/2000, m-ans%2000, ans%2000);
55     } 
56     return 0;
57 }
View Code
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int maxn = 1010;
 4 const int M = 2000;
 5 vector<int> G[maxn];
 6 int dp[maxn][2];
 7 bool vis[maxn];
 8 void dfs(int x){
 9     vis[x] = true;
10     dp[x][0] = 0; dp[x][1] = M;
11     for(int i=0; i<G[x].size(); i++){
12         int v = G[x][i];
13         if(vis[v]) continue;
14         dfs(v);
15         dp[x][0] += dp[v][1] + 1;
16         dp[x][1] += min(dp[v][1], dp[v][0]+1);
17     }
18 }
19 int main(){
20     //freopen("in.txt", "r", stdin); 
21     int t; scanf("%d", &t);
22     while(t--){
23         int n, m; scanf("%d%d", &n, &m);
24         for(int i=0;i<=n;i++)G[i].clear();
25         for(int i=0; i<m; i++){
26             int a, b;scanf("%d%d", &a, &b);
27             G[a].push_back(b); G[b].push_back(a);
28         }
29         memset(vis, 0, sizeof(vis));
30         int ans = 0; 
31         for(int i=0; i<n; i++){
32             if(vis[i]) continue;
33             dfs(i);
34             ans += min(dp[i][0], dp[i][1]);
35         }
36         printf("%d %d %d\n", ans/M, m-ans%M, ans%M);
37     }
38     return 0;
39 }
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM