SGU 206 Roads

http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=10032

题目大意

这题目略长啊…简化题意是说给你一个n个点,m条边的无向图,每条边有边权。现在要你修改一些边的边权,使得前n-1条边是最小生成树,要求总的修改量最小,输出修改后每条边的边权。

 

解题报告

好题。从题意看是最小生成树相关的题目,其实这道题的正解是匹配。

设xi为每条边的边权,di为每条边的修改量。很容易想到要让前n-1条边成为最小生成树,那么必然是让前n-1条边权值改小,其它的边权值改大。即对于一条权值为x1,修改量为d1的树边 和一条权值为x2,修改量为d2的非树边,有: x1-d1<=x2+d2。将这个不等式变形,即 d1+d2>=x1-x2 右边是一个常数,要让左边尽量小,即左右相等的时候。观察这个式子,会发现它和我们做km时顶标和那个式子(lx[i]+ly[j]>=w[i][j])有些相似。因此,假如有一条非树边<i,j>,我们可以把从i到j的树边和这条非树边看作二分图的左右节点a,b,连一条权值为x[a]-x[b]的边。把图建出来再做一次km就可以了。

图论模型真心各种灵活,太考想象力了……

 

代码

View Code
 1 #include<cstdio>
 2 #include<cstdlib>
 3 #include<cstring>
 4 #include<bitset>
 5 using namespace std;
 6 #define maxn 100
 7 #define maxm 500
 8 
 9 int n,m,w[maxm][maxm],g[maxn][maxn],lx[maxm],ly[maxm],l[maxm],a[maxm],b[maxm],z[maxm],slack[maxm];
10 bitset <maxm> ux,uy;
11 
12 bool find(int x)
13 {
14     ux[x]=1;
15     for (int i=1;i<=m;i++)
16     {
17         if (!uy[i])
18         {
19             int t=lx[x]+ly[i]-w[x][i];
20             if (t==0)
21             {
22                 uy[i]=1;
23                 if (!l[i] || find(l[i]))
24                 {
25                     l[i]=x;
26                     return 1;
27                 }
28             }
29             else if (slack[i]>t) slack[i]=t;
30         }
31     }
32     return 0;
33 }
34 void km()
35 {
36     memset(lx,-0x3f,sizeof(lx));
37     memset(ly,0,sizeof(ly));
38     memset(l,0,sizeof(l));
39     for (int i=1;i<=m;i++)
40         for (int j=1;j<=m;j++)
41             if (w[i][j]>lx[i]) lx[i]=w[i][j];
42     for (int j=1;j<=m;j++)
43     {
44         memset(slack,0x3f,sizeof(slack));
45         while (1)
46         {
47             ux.reset();
48             uy.reset();
49             if (find(j)) break;
50             int d=0x3f3f3f3f;
51             for (int i=1;i<=m;i++)
52                 if (!uy[i] && slack[i]<d) d=slack[i];
53             for (int i=1;i<=m;i++)
54                 if (ux[i]) lx[i]-=d;
55             for (int i=1;i<=m;i++)
56             {
57                 if (uy[i]) ly[i]+=d;
58                 else slack[i]-=d;
59             }
60         }
61     }
62 }
63 bool dfs(int pre,int u,int v,int id)
64 {
65     if (u==v) return 1;
66     for (int i=1;i<=n;i++)
67     {
68         if (i==pre || !g[u][i]) continue;
69         if (dfs(u,i,v,id))
70         {
71             w[g[u][i]][id]=z[g[u][i]]-z[id];
72             return 1;
73         }
74     }
75     return 0;
76 }
77 
78 int main()
79 {
80     scanf("%d%d",&n,&m);
81     for (int i=1;i<=m;i++)
82     {
83         int x,y;
84         scanf("%d%d%d",&x,&y,&z[i]);
85         a[i]=x,b[i]=y;
86     }
87     for (int i=1;i<n;i++) g[a[i]][b[i]]=g[b[i]][a[i]]=i;
88     memset(w,0,sizeof(w));
89     for (int i=n;i<=m;i++)
90         dfs(0,a[i],b[i],i);
91     km();
92     for (int i=1;i<n;i++) printf("%d\n",z[i]-lx[i]);
93     for (int i=n;i<=m;i++) printf("%d\n",z[i]+ly[i]);
94     return 0;
95 }

 

转载于:https://www.cnblogs.com/wangziyun/archive/2013/03/31/2991389.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值