P5633 最小度限制生成树
题目

分析
\(wqs\)二分经典题。
原函数显然是一个上凸包,于是考虑\(wqs\)二分。
可以考虑把边集分成两类,一类是和\(s\)直接相连的,一类是和\(s\)不连接的。
然后显然我们每一次只会改变前者的值,这样我们可以只在最开始的地方给两类边分别排好序,然后每次归并即可。
接下来就是一般最小生成树的过程了。
这样时间复杂度是\(O(n\log n)\)的。
代码
#include<bits/stdc++.h>
using namespace std;
//#define getchar()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
//char buf[1<<21],*p1=buf,*p2=buf;
template <typename T>
inline void read(T &x){
x=0;char ch=getchar();bool f=false;
while(!isdigit(ch)){if(ch=='-'){f=true;}ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return ;
}
template <typename T>
inline void write(T x){
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10^48);
return ;
}
#define ll long long
const int N=5e5+5;
const ll INF=1e9;
int n,m,k,s;
struct Edge{
int u,v;ll w;
inline bool operator < (const Edge &B)const{return w<B.w;}
Edge(int u=0,int v=0,ll w=0):u(u),v(v),w(w){}
}E1[N],E2[N],E[N];
int fa[N],top1,top2;
int Getfa(int x){return fa[x]==x?x:fa[x]=Getfa(fa[x]);}
ll sum,tot,cnt;
bool Kruskal(ll mid){
tot=sum=m=cnt=0;
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=top1;i++) E1[i].w+=mid;
int j=1;
for(int i=1;i<=top2;i++){
while(j<=top1&&E1[j].w<=E2[i].w) E[++m]=E1[j],j++;
E[++m]=E2[i];
}
while(j<=top1) E[++m]=E1[j++];
for(int i=1;i<=m;i++){
int u=Getfa(E[i].u),v=Getfa(E[i].v);
if(u==v) continue;
fa[u]=v;sum+=E[i].w;
if(E[i].u==s||E[i].v==s) tot++;
if(cnt==n-1) return tot>=k;
}
for(int i=1;i<=top1;i++) E1[i].w-=mid;
return tot>=k;
}
signed main(){
read(n),read(m);read(s),read(k);
for(int i=1,u,v,w;i<=m;i++){
read(u),read(v),read(w);
if(u==s||v==s) E1[++top1]=Edge(u,v,w);
else E2[++top2]=Edge(u,v,w);
}
sort(E1+1,E1+top1+1),sort(E2+1,E2+top2+1);
ll l=-INF,r=INF,ans=-1;
if(!Kruskal(l)){puts("Impossible");return 0;}
if(Kruskal(r)&&tot>k){puts("Impossible");return 0;}
while(l<r){
ll mid=l+r+1>>1;
if(Kruskal(mid)) l=mid;
else r=mid-1;
}
Kruskal(r);
write(sum-k*r);
return 0;
}
感受
这个分两类边排序然后归并的技巧很巧妙,直接省下了1个\(\log\),可以记下。

浙公网安备 33010602011771号