bzoj2654 (二分+krusal)

Description

  给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有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


Sample Output

2

HINT

数据规模和约定

  0:V<=10

  1,2,3:V<=15

  0,..,19:V<=50000,E<=100000

  所有数据边权为[1,100]中的正整数。

题解:显然可以发现随着白边权值的增大。最小生成树中白边的个数不增。

然后根据这个性质我们就可以二分一个值,然后每次给白边加上这个值。看一下最小生成树中白边的个数。

最后答案再把它减去。

看起来思路非常简单,但是有一个很重要的细节。

如果在你的二分过程中如果给白边加上mid,你得到的白边数比need大。

给白边加上mid+1,你得到的白边比need小。

这种情况看似没法处理。

但是考虑一下克鲁斯卡尔的加边顺序。

可以发现如果出现这种情况,一定是有很多相等的白边和黑边。因为数据保证合法。

所以我们可以把一些白边替换成黑边。

所以我们要在白边数>=need的时候跟新答案。

具体用ans=ans-mid*need;即可。

白边边数少了减权值

多了加权值

小疑惑f(x)不一定是单调函数 满足大于等于关系 万一达不到上述边界结束条件

 1 #include<cstdio>
 2  
 3 #include<algorithm>
 4  
 5 #include<iostream>
 6  
 7 using namespace std;
 8  
 9 int fa[1000001],n,m,need,l(-105),r(105),mid,temp,ans,r1,r2,cnt,ans2;
10  
11 struct use{int st,en,c,v;}e[100010];
12  
13 int find(int x){if (x!=fa[x]) fa[x]=find(fa[x]);return fa[x];}
14  
15 bool cmp(use a,use b){if (a.v==b.v) return a.c<b.c;else return a.v<b.v;}
16  
17 void solve(){
18  
19 sort(e+1,e+m+1,cmp);
20  
21 for (int i=1;cnt!=n-1;i++){
22  
23 r1=find(e[i].st);r2=find(e[i].en);
24  
25 if (r1!=r2){fa[r1]=r2;cnt++;if (e[i].c==0) temp++;ans+=e[i].v;}
26  
27 }
28  
29 }
30  
31 int main(){
32  
33 scanf("%d%d%d",&n,&m,&need);
34  
35 for (int i=1;i<=m;i++){
36  
37 scanf("%d%d%d%d",&e[i].st,&e[i].en,&e[i].v,&e[i].c);
38  
39 e[i].st++;e[i].en++;
40  
41 }
42  
43 while (l<=r){
44  
45 mid=(l+r)>>1;
46  
47 for (int i=1;i<=m;i++) {if (e[i].c==0) e[i].v+=mid;}
48  
49 for (int i=1;i<=n+1;i++) fa[i]=i;ans=0;temp=0;cnt=0;
50  
51 solve();
52  
53 if (temp>=need) {l=mid+1;ans2=ans-need*mid;}else r=mid-1;
54  
55 for (int i=1;i<=m;i++) if (e[i].c==0) e[i].v-=mid;
56  
57 }
58  
59 printf("%d\n",ans2);
60  
61 } 

update

转载自https://www.cnblogs.com/NaVi-Awson/p/7252243.html

 1 #include<map>
 2 #include<ctime>
 3 #include<cmath>
 4 #include<queue>
 5 #include<stack>
 6 #include<cstdio>
 7 #include<string>
 8 #include<vector>
 9 #include<cstring>
10 #include<cstdlib>
11 #include<iostream>
12 #include<algorithm>
13 #define LL long long
14 #define RE register
15 #define IL inline
16 using namespace std;
17 const int V=50000;
18 const int E=100000;
19 
20 int mid;
21 int v,e,need,ans,cnt,tmp;
22 struct tt
23 {
24     int u,v,c,col;
25 }edge[E+5];
26 
27 IL int Kruskal();
28 bool comp(const tt &a,const tt &b) {return a.c+(a.col^1)*mid<b.c+(b.col^1)*mid;}
29 
30 int set[V+5];
31 IL int find(int r) {return set[r]!=-1 ? set[r]=find(set[r]):r;}
32 
33 int main()
34 {
35     scanf("%d%d%d",&v,&e,&need);
36     for  (RE int i=1;i<=e;i++) scanf("%d%d%d%d",&edge[i].u,&edge[i].v,&edge[i].c,&edge[i].col);
37     int l=-100,r=100;
38     while (l<=r)
39     {
40         mid=(l+r)>>1;
41         if (Kruskal()>=need) l=mid+1,ans=tmp;
42         else r=mid-1;
43     }
44     printf("%d\n",ans);
45     return 0;
46 }
47 
48 IL int Kruskal()
49 {
50     tmp=cnt=0;
51     int k=0;
52     memset(set,-1,sizeof(set));
53     sort(edge+1,edge+1+e,comp);
54     for (RE int i=1;i<=e;i++)
55     {
56         int q=find(edge[i].u);
57         int p=find(edge[i].v);
58         if (p!=q)
59         {
60             k+=edge[i].col^1;
61             set[q]=p;
62             cnt++;
63             tmp+=edge[i].c;
64             if (cnt==v-1) break;
65         }
66     }
67     return k;
68 }

我们发现,如果我们给白边增加权值,做最小生成树,由于白边权值增大,导致不容易选白边。记f(x)f(x)为给白边增加xx(xx可为负)权值,做最小生成树后,选白边的数量。可以发现,f(x)f(x)随xx增大而减小,显然可以二分。
其次,我们发现,由于黑边的权值是不变的,与白边权值不相互影响。同样由于白边之间关系相对不变,必然选出的needneed条白边一定是符合题意的。

由于COGSCOGS数据会有不满足恰好needneed条白边的情况(改边前不满足)

打个比方有这样的数据:加00时大于needneed,加11就小于needneed了。

这样应该在跑最小生成树的时候把所有的白边都加上加的那个权值,结果就是最小生成树的权值和减去needneed∗加上的权值,多出来的那一部分完全可以当做黑边来看,因为数据是100000100000的,这样就可以了。

排序的时候,如果边权相同,要把白边放在前面。

要计算当前至多能取多少白边,当然要把白边放前面。由于保证有解,在cnt>=needcnt>=need且cntcnt取最小值的方案下,一定能有黑边把多余的白边代替掉。

猜你喜欢

转载自www.cnblogs.com/wyh447154317/p/9332515.html