zoukankan      html  css  js  c++  java
  • 树形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
    ", 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
    ", 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
    ", ans[i]);
    49         }
    50     }else{
    51         printf("NONE
    ");
    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
    ", 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
    ", 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
    ", 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
    ", 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
    ", (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
    ", 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
    ", ans/M, m-ans%M, ans%M);
    37     }
    38     return 0;
    39 }
    View Code
  • 相关阅读:
    xss漏洞
    web日志分析(待)
    linux命令学习摘记
    浏览器的MIME映射(程序映射)
    文件上传靶场-Upload-Labs
    目录遍历用字典
    cmd、bat分割单行字符串
    iptables使用
    Spring AOP 学习(五)
    Spring 使用注解注入 学习(四)
  • 原文地址:https://www.cnblogs.com/seaupnice/p/9471700.html
Copyright © 2011-2022 走看看