emmm……
今天第二题正解代码有点复杂,我先写第三题
【题目】
题目描述:
给你一个有 N 个点 M 条边的无向带权连通图,每条边是白色或黑色,求一颗最小权的恰好有 K 条白边的生成树。
输入格式:
第一行三个数 N、M、K ,分别表示点数、边数和所需的白边数。
接下来 M 行,每行四个数 u、v、w、c ,分别表示一条边的两个端点(从0开始标号)、边权和颜色(0表示白色,1表示黑色)。
输入数据保证有解。
输出格式:
一行一个数表示所求生成树的边权和。
样例数据:
输入
2 2 1
0 1 1 1
0 1 2 0输出
2
备注:
【数据范围】
对 20% 的输入数据 :1≤N≤15。
对 100% 的输入数据 :1≤N≤50000,1≤M≤100000,1≤w≤100。
【分析】
看到这道题,很容易想到最小生成树,但是加上了选 K 条白边的限制,这该怎么办呢?
如果直接用最小生成树的话,不能够保证能选 K 条白边,我们就要通过一个操作让它选 K 条边
正解的做法就是将所有白边的权值加上(或减去)一个值,使得这样做了之后最小生成树能恰好选出 K 条白边
怎么确定这个值呢?用二分,由于边权最多为 100,所以我们就在 -100 ~ 100 之间二分
在计算答案的时候将二分出的值补回去就可以了
至于证明嘛……大家感性理解一下就行了,因为本蒟蒻也不会
【代码】
这里注意一下代码中易错的两个地方:
- 排序时,在权值相等的情况下,要么让白边全在前面,要么让黑边全在前面,不然有可能会破坏二分的单调性
- 在二分完输出答案前,还要做一遍最小生成树,不然的话,答案是最后一次二分时的答案,而不是最终答案
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200005
using namespace std;
int n,m,k,sum;
int x[N],y[N],z[N],c[N],father[N];
struct node
{
int u,v,w,c;
}a[N];
bool comp(const node &p,const node &q)
{
if(p.w!=q.w)
return p.w<q.w;
return p.c<q.c;
}
int find(int x)
{
if(father[x]!=x)
father[x]=find(father[x]);
return father[x];
}
void add(int x,int y,int z,int c,int i)
{
a[i].u=x;
a[i].v=y;
a[i].w=z;
a[i].c=c;
}
bool Kruskal(int mid)
{
int i,p,q,num=0,ans=0;
for(i=0;i<n;++i)
father[i]=i;
for(i=1;i<=m;++i)
{
add(x[i],y[i],z[i],c[i],i);
if(c[i]==0)
a[i].w+=mid;
}
sort(a+1,a+m+1,comp);
sum=0;
for(i=1;i<=m;++i)
{
p=find(a[i].u);
q=find(a[i].v);
if(p!=q)
{
num++;
sum+=a[i].w;
father[p]=q;
if(a[i].c==0)
ans++;
}
if(num==n-1)
break;
}
if(ans>=k)
return true;
return false;
}
int main()
{
// freopen("tree.in","r",stdin);
// freopen("tree.out","w",stdout);
int l,r,i,mid;
scanf("%d%d%d",&n,&m,&k);
for(i=1;i<=m;++i)
scanf("%d%d%d%d",&x[i],&y[i],&z[i],&c[i]);
l=-100,r=100;
while(l<r)
{
mid=(l+r+1)>>1;
if(Kruskal(mid)) l=mid;
else r=mid-1;
}
Kruskal(l);
printf("%d",sum-k*l);
// fclose(stdin);
// fclose(stdout);
return 0;
}