[bzoj2654] tree
Description
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有 \(need\) 条白色边的生成树。
题目保证有解。
Input
第一行\(V\) ,\(E\) ,\(need\) 分别表示点数,边数和需要的白色边数。
接下来 \(E\) 行,每行 \(s\),\(t\),\(c\),\(col\) 表示这边的端点(点从0开始标号),边权,颜色(0白色1黑色)。
Output
一行表示所求生成树的边权和。
\(V \leq 50000\) , \(E\leq 100000\) ,所有数据边权为 \([1,100]\) 中的正整数。
Sample Input
2 2 1
0 1 1 1
0 1 2 0
Sample Output
2
题解
对本蒟蒻来说这是一道做法很新奇很高级的题……
感性理解,如果给所有白边都加上一个正值 \(mid\) ,再跑最小生成树,其中的白边数目会减少(\(vice\) \(versa\))
那么可以二分给所有白边加的值 \(mid\) ,跑 \(kruskal\) ,直到白边数目为 \(need\) ,用此时最小生成树值 \(-mid \times need\) 就是最终答案
还有一些小细节(下面是从大神博客中引用来的)
但是你可能怀疑二分的正确性?即如果给白色边边权加上 \(mid\),则所选白色边 \(>need\),如果加上 \(mid+1\),则所选白色边 \(<need\) 。这种情况看似没法处理。但是考虑一下克鲁斯卡尔的加边顺序。可以发现如果出现这种情况,一定是有很多相等的白边和黑边。因为数据保证合法。所以我们可以把一些白边替换成黑边。所以我们要在白边数 \(\leq need\)的时候跟新答案。
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 50005;
struct node{
int v,len,col;
node *nxt;
}pool[N*4],*h[N],*ed[N*4];
int cnt;
void addedge(int u,int v,int len,int col){
node *p=&pool[++cnt],*q=&pool[++cnt];
p->v=v;p->nxt=h[u];h[u]=p; p->len=len;p->col=col;
q->v=u;q->nxt=h[v];h[v]=q; q->len=len;q->col=col;
}
int tot;
int n,m,need;
int w[205];
struct data{
int v,len,col;
data(int v=0,int len=0,int col=0): v(v),len(len),col(col) {}
bool operator < (const data &b) const { return len>b.len; }
};
priority_queue<data> que;
int d[N];
int prim(int x){
int u,v,c,ret=0;
for(int i=0;i<n;i++) d[i]=1e8;
que.push(data(0,d[0],1));
while(!que.empty()){
u=que.top().v; c=que.top().col;
que.pop();
if(!d[u]) continue;
ret+=1-c; w[x]+=d[u]; d[u]=0;
for(node *p=h[u];p;p=p->nxt)
if(d[v=p->v]!=0 && d[v]>p->len){
d[v]=p->len;
que.push(data(v,d[v],p->col));
}
}
w[x]-=1e8;
return ret;
}
int check(int x){
for(int i=0;i<tot;i++) ed[i]->len+=x;
int ret=prim(x+100);
w[x+100]-=ret*x;
for(int i=0;i<tot;i++) ed[i]->len-=x;
return ret;
}
int main()
{
int x,y,z,c;
scanf("%d%d%d",&n,&m,&need);
for(int i=0;i<m;i++){
scanf("%d%d%d%d",&x,&y,&z,&c);
addedge(x,y,z,c);
if(c==0) ed[tot++]=&pool[cnt],ed[tot++]=&pool[cnt-1];
}
int l=-100,r=100,mid;
while(l<r){
mid=(l+r+1)>>1;
if(check(mid)<need) r=mid-1;
else l=mid;
}
printf("%d\n",w[l+100]);
return 0;
}
既然选择了远方,便只顾风雨兼程