【国家集训队】Tree I
【国家集训队】Tree I
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
Data Constraint
对于 \(5\%\) 的数据,\(V\leq 10\)。
对于另 \(15\%\) 的数据,\(V\leq 15\)。
对于 \(100\%\) 的数据,\(V\leq 5\times10^4,E\leq 10^5\)。
所有数据边权为 \([1,100]\) 中的正整数。
Solution
这个是wqs的板子,所以不讲做法了
由于是第一次写,发现了很多问题,写个题解总结一下
涉及排序一定要想清楚它跟凸包的关系
比如这题中一定要钦定权值相同先选哪个颜色
因为一条直线会截得一整段,只有钦定才能得知选的是最靠右还是最靠左
二分写法问题
这题甚至使我改变了以往的二分写法。。。
采用\(l=mid-1,r=mid+1,lst=mid\)的写法
一般来说,不需要二分小数
因为分界点之间的纵坐标的差一般是整数
答案是加上\(m*k\)(\(m\)为物品个数,\(k\)为斜率)
而不是任意选中我们实际选择的物品数量
这是因为直线切的是包含\(m\)的一整段
Code
#include<bits/stdc++.h>
using namespace std;
#define F(i,a,b) for(int i=a;i<=b;i++)
#define Fd(i,a,b) for(int i=a;i>=b;i--)
#define N 100010
int v,e,n,ans,f[N];
struct node{
int st,en,v,col;
bool operator<(const node&x)const{return v<x.v||v==x.v&&col<x.col;}
};
vector<node>edge,a;
int get(int x){return f[x]==x?x:(f[x]=get(f[x]));}
int check(int x){
int res=0,sum=0;
a.clear();
for(auto d:edge){
if(!d.col)a.push_back((node){d.st,d.en,d.v-x,d.col});
else a.push_back(d);
}
sort(a.begin(),a.end());
F(i,0,v-1)f[i]=i;
int cnt=0;
for(auto d:a){
int A=get(d.st),B=get(d.en);
if(A==B)continue;cnt++;
f[A]=B;
sum+=d.v;
if(!d.col)res++;
}
ans=sum;
return res;
}
int main(){
scanf("%d%d%d",&v,&e,&n);
F(i,1,e){
int A,B,C,D;
scanf("%d%d%d%d",&A,&B,&C,&D);
edge.push_back((node){A,B,C,D});
}
int l=-100,r=100,mid,lst;
while(l<=r){
mid=l+r>>1;
int w=check(mid);
if(w>=n)lst=mid,r=mid-1;
else l=mid+1;
}
check(lst);
printf("%d",ans+n*lst);
return 0;
}

浙公网安备 33010602011771号