Description
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。
题目保证有解。
Input
第一行V,E,need分别表示点数,边数和需要的白色边数。
接下来E行
每行s,t,c,col表示这边的端点(点从0开始标号),边权,颜色(0白色1黑色)。
Output
一行表示所求生成树的边权和。
Sample Input
0 1 1 1
0 1 2 0
Sample Output
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了。
这样应该在跑最小生成树的时候把所有的白边都加上加的那个权值,结果就是最小生成树的权值和减去need∗need∗加上的权值,多出来的那一部分完全可以当做黑边来看,因为数据是100000100000的,这样就可以了。
排序的时候,如果边权相同,要把白边放在前面。
要计算当前至多能取多少白边,当然要把白边放前面。由于保证有解,在cnt>=needcnt>=need且cntcnt取最小值的方案下,一定能有黑边把多余的白边代替掉。