题解:P16146 [ICPC 2017 NAIPC] Blazing New Trails
RB211_535E4B · · 题解
对于“特别-普通”的小路进行标记,题目转化为至少包含
某个权值改变可以控制取数个数的,且结果有单调性的使用 WQS 二分。此题对于被标记小路加一个权值,然后跑 MST,权值大会少选被标记小路,权值小会多选被标记小路。
此题另一难点在于判断不可能的情况。如果到输出答案的时候按所选取的权值再跑一遍,(由于有一些边权一样的边答案是一个区间)答案区间中如果还没有包含要求个数那么就输出不可能。在一开始输入的时候判断一下给定的全选是否联通,不联通也输出不可能。
代码如下,实际上就是一个板子,连 DP 都不是。
#include <bits/stdc++.h>
using namespace std;
#define int long long
struct node{
int u,v,w,col;
}a[500005],b[500005];
int fa[200005];
int n,m,need;
int min_ = 1e18;
int find(int x){return (x==fa[x]?x:fa[x] = find(fa[x]));}
bool cmp(node x, node y){
if (x.w == y.w) return x.col > y.col;//优先选黑的
return x.w< y.w;
}
bool cmp2(node x, node y){
if (x.w == y.w) return x.col < y.col;//优先选白的
return x.w< y.w;
}
void _init_(int x,int id){
//初始化
for (int i = 1; i <= n; i++)fa[i] = i;
set<tuple<int,int,int>> vis;
//加权
for (int i = 1; i <= m; i++){
b[i].u = a[i].u,b[i].v = a[i].v,b[i].col = a[i].col;
if (!a[i].col) {
b[i].w = a[i].w+x;
vis.insert({b[i].u,b[i].v,b[i].w}),vis.insert({b[i].v,b[i].u,b[i].w});
}//黑边全为正,所以白边为负的一定没有匹配(还有图是无向的)
if (a[i].col) b[i].w = a[i].w;
}
if (id) sort(b+1,b+1+m, cmp);
else sort(b+1,b+1+m, cmp2);
}
int check(int x,int id){
_init_(x,id);
//跑MST
int ans = 0,sum = 0;
for (int i = 1; i <= m; i++){
int px = find(b[i].u),py = find(b[i].v);
if (px != py){
fa[px] = py;
sum += b[i].w;
ans += (b[i].col==0);
}
}
if (need >= ans) min_=sum-need*x;
return ans;
}
bool num[200006];
signed main(){
int k;
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m >> k >> need;
for (int i = 1; i <= k; i++){int x;cin >> x,num[x]=1;}
for (int i = 1; i <= m; i++){
cin >> a[i].u >> a[i].v >> a[i].w;
if (num[a[i].u]^num[a[i].v]) a[i].col = 0;
else a[i].col =1;
}
for (int i = 1; i <= n; i++)fa[i] = i;
for (int i = 1; i <= m; i++){
int px = find(a[i].u), py = find(a[i].v);
if (px != py) fa[px] = py;
}
int cnt = 0;
for (int i = 1; i <= n; i++) cnt +=(fa[i] == i);
if (cnt != 1) {cout <<-1;return 0;}
int l = -100000,r = 100000,id = 0;
while (l <= r){
int mid = (l+r)/2;
int tmp =check(mid,1);
if (need < tmp) l = mid+1;
if (need >= tmp) id = mid,r = mid-1;
}
int ansl = check(id,1),ansr = check(id,0);
if (ansl <= need && ansr >= need)cout << min_;
else cout << -1;
return 0;
}