真正的危机不是机器人像人一样思考,而是人像机器一样思考。 ——凉宫春日的忧郁

[国家集训队2012]tree(陈立杰)

[国家集训队2012]tree(陈立杰)

题目

给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。
题目保证有解。

INPUT

第一行V,E,need分别表示点数,边数和需要的白色边数。
接下来E行
每行s,t,c,col表示这边的端点(点从0开始标号),边权,颜色(0白色1黑色)。

OUTPUT

一行表示所求生成树的边权和。

SAMPLE

INPUT

2 2 1

0 1 1 1

0 1 2 0

OUTPUT

2

数据规模

0:V<=10
1,2,3:V<=15
0,..,19:V<=50000,E<=100000
所有数据边权为[1,100]中的正整数。

解题报告

国家集训队的题,果然是好题= =

首先,我们观察题面,显然与最小生成树有什么关系,但是,直接跑最小生成树显然也是不合理的,那么问题就在于,如何在跑最小生成树的同时,还能保证白边的个数?

正解是个很神奇的东西——二分

首先我们想怎么二分,我们注意到,边权的范围很小,是$[1,100]$,那么,我们是否可以通过控制白边的边权,达到在最小生成树中控制白边的数量呢?

显然可以。

我们以$Kruskal$算法为例,我们进行$Kruskal$时,是以每条边的边权为依据,进行从小到大排序,然后从小到大取出各个边,不断加入连通分量中,最后形成最小生成树。那么,当我们改变某一些边的权值时,我们按权值排序得出的边的序列也一定就会不一样,那么,我们就可以通过控制权值来控制加入生成树的白边数了。

具体做法:

在$[-100,100]$中二分得到$mid$,让所有白边的权值加上该$mid$值,跑$Kruskal$,直到得到最终结果

但是,只是这样就可以了吗?

显然不是。

我们考虑,假如我们点很少,只有$10+$个点,但是我们的边很多,达到了$100000$,而且权值范围还被限制在了$[1,100]$,那么显然,会有许多等价的黑边与白边,即使加上了某一个权值,也可能会有很多白边与很多黑边相等价,当我们按照权值排序的同时,我们把黑边与白边混在了一起,然后我们就开始了$Kruskal$,那样的话,我们本来可以得到刚好$need$条白边,我们却把一些与黑边等价的白边扔进了生成树中,这样的话,我们本来可以得到最优解,却认为它是不合法的。

这时我们就需要处理一下这些等价的边。

具体做法:

在二分后判断时,我们不单单判断此时白边的数目是否等于$need$,而是将一切白边数大于等于$need$的情况全部考虑上,然后,我们加上了(或者是减去,因为我们有负数)许多为了控制生成树的权值,所以我们需要减去(或加上)这些权值,我们并不能将所有白边修改的权值修改回去,而是将$need\times mid$权值处理掉,因为假如我们处理所有的白边,我们可能将那些等价于黑边的白边也处理掉了,也就是相当于处理了黑边,显然是不合法的,所以我们只处理$need$条,也就是真正被当成白边的白边数量

 1 #include<algorithm>
 2 #include<iostream>
 3 #include<cstring>
 4 #include<cstdio>
 5 using namespace std;
 6 inline int read(){
 7     int sum(0),f(1);
 8     char ch(getchar());
 9     for(;ch<'0'||ch>'9';ch=getchar())
10         if(ch=='-')
11             f=-1;
12     for(;ch>='0'&&ch<='9';sum=sum*10+(ch^48),ch=getchar());
13     return sum*f;
14 }
15 struct edge{
16     int s,e,w,col,tmp;
17     friend bool operator<(const edge &a,const edge &b){
18         return a.tmp==b.tmp?a.col<b.col:a.tmp<b.tmp;
19     }
20 }a[100005];
21 int n,m,need;
22 int fa[50005];
23 inline int find(int x){
24     if(fa[x]==x)
25         return x;
26     fa[x]=find(fa[x]);
27     return fa[x];
28 }
29 int tp;
30 inline bool krus(){
31     tp=0;
32     int tot(0),whit(0);
33     sort(a+1,a+m+1);
34     for(int i=1;i<=m;++i){
35         int s(a[i].s),e(a[i].e);
36         int fs(find(s)),fe(find(e));
37         if(fs!=fe){
38             fa[fe]=fs;
39             if(a[i].col==0)
40                 ++whit;
41             ++tot;
42             tp+=a[i].tmp;
43             if(tot==n-1)
44                 break;
45         }
46     }
47     return whit>=need;
48 }
49 inline int gg(){
50     freopen("nt2012_tree.in","r",stdin);
51     freopen("nt2012_tree.out","w",stdout);
52     n=read(),m=read(),need=read();
53     for(int i=1;i<=m;++i)
54         a[i].s=read()+1,a[i].e=read()+1,a[i].w=read(),a[i].col=read();
55     int l(-105),r(105),ans;
56     while(l<=r){
57         int mid((l+r)>>1);
58         for(int i=1;i<=n;++i)
59             fa[i]=i;
60         for(int i=1;i<=m;++i){
61             if(a[i].col==0)
62                 a[i].tmp=a[i].w+mid;
63             else
64                 a[i].tmp=a[i].w;
65         }
66         if(krus())
67             l=mid+1,ans=tp-need*mid;
68         else
69             r=mid-1;
70     }
71     printf("%d",ans);
72     return 0;
73 }
74 int K(gg());
75 int main(){;}
View Code

 

posted @ 2017-08-14 13:06  Hzoi_Mafia  阅读(3439)  评论(0编辑  收藏  举报
我们都在命运之湖上荡舟划桨,波浪起伏着而我们无法逃脱孤航。但是假使我们迷失了方向,波浪将指引我们穿越另一天的曙光。 ——死神