题目大意
给你
n
n
n 个点,
m
m
m 条边,每条边都有两个值
c
i
,
d
i
c_i, d_i
ci,di,代表原始花费和折扣后的花费,问你使用
k
k
k 条折扣后的边使这
n
n
n 个点联通,最小花费是多少。对于
k
∈
[
0
,
n
−
1
]
k
∈
Z
k \in [0,n-1] \ k\in Z
k∈[0,n−1] k∈Z 每一个值你都需要回答,题目保证有解。
2
≤
n
≤
1000
,
n
−
1
≤
m
≤
2
×
1
0
5
,
1
≤
d
i
≤
c
i
≤
1000
2\leq n\leq1000, n-1\leq m\leq 2\times 10^5,1\leq d_i \leq c_i \leq 1000
2≤n≤1000,n−1≤m≤2×105,1≤di≤ci≤1000
解题思路
记原始花费边为白边,折扣后的边为黑边,我们对白边和黑边分别跑一次最小生成树,这样所有被用到的边就是答案可能用到的边,这时边的规模减少到了
2
×
(
n
−
1
)
2\times(n-1)
2×(n−1)。
我们知道一个很经典的问题,就是问你恰好选
k
k
k 条黑边的最小花费是多少,那个问题的做法是二分+最小生成树,给黑边加上一个很大的值
c
c
c,然后每次二分
c
c
c 来
c
h
e
c
k
check
check 答案。
在这里我们注意到
c
≤
1000
c \leq 1000
c≤1000 ,我们可以对每一个
c
c
c 跑一次最小生成树,每次求得
{
s
u
m
,
n
u
m
}
\{sum, num\}
{sum,num},即花费和使用黑边的条数。这时答案是
s
u
m
−
c
×
n
u
m
sum - c\times num
sum−c×num
我们从
0
0
0 到
1000
1000
1000 遍历
c
c
c,显然我们
c
c
c 越小,选取的黑边数越多,所以我们在寻找答案的时候,找到第一个满足条件的
n
u
m
num
num,此时答案就是
s
u
m
i
−
c
×
k
sum_i - c \times k
sumi−c×k
为什么不是
s
u
m
i
−
c
×
n
u
m
i
sum_i - c \times num_i
sumi−c×numi 呢?,这时因为如果当
c
=
c
0
c = c_0
c=c0 时选取了
r
r
r 条黑边,
c
=
c
0
+
1
c = c_0 + 1
c=c0+1 时选取了
l
l
l 条黑边,这时,我们选取
l
≤
k
≤
r
l\leq k\leq r
l≤k≤r 条黑边也是合法的,因为选出
l
l
l 条黑边的原因是,当黑边加上
c
c
c 等于白边的花费时,我们优先选择白边,所以乘以
k
k
k 才是最优解。
Code
#include <bits/stdc++.h>
#define ll long long
#define qc ios::sync_with_stdio(false); cin.tie(0);cout.tie(0)
#define fi first
#define se second
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define pb push_back
using namespace std;
const int MAXN = 1e3 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int fa[MAXN];
void init(){
for (int i = 0; i <= MAXN-1; ++i){
fa[i] = i;
}
}
int find(int x){
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y){
x = find(x);
y = find(y);
if(x != y)
fa[y] = x;
}
int n, m;
struct node{
int u, v, w;
int f;
bool operator<(node b) const {
if(w != b.w)
return w < b.w;
return f < b.f;
}
};
const int P = 1000;
PII ans[MAXN << 1];
void solve(){
cin >> n >> m;
memset(ans, 0, sizeof ans);
vector<node> v1, v2;
for (int i = 1; i <= m; ++i){
node tmp;
cin >> tmp.u >> tmp.v >> tmp.w;
tmp.f = 0;
v1.pb(tmp);
cin >> tmp.w;
tmp.f = 1;
v2.pb(tmp);
}
init();
sort(v1.begin(), v1.end());
vector<node> v;
for(auto it : v1){
if(find(it.u) != find(it.v)){
merge(it.u, it.v);
v.pb(it);
}
}
init();
sort(v2.begin(), v2.end());
for(auto it : v2){
if(find(it.u) != find(it.v)){
merge(it.u, it.v);
v.pb(it);
}
}
sort(v.begin(), v.end());
for(int c = 0; c <= 1000; c++){
init();
int sum = 0;
int num = 0;
vector<node> vv;
for(auto it : v){
node tmp = it;
if(tmp.f) tmp.w += c;
vv.pb(tmp);
}
sort(vv.begin(), vv.end());
for(auto it : vv){
if(find(it.u) != find(it.v)){
merge(it.u, it.v);
sum += it.w;
num += it.f;
}
}
ans[c] = {sum, num};
}
for(int i = 0; i < n; i++){
for(int j = 0; j <= P; j++){
if(ans[j].se <= i){
cout << ans[j].fi - j*i << "\n";
break;
}
}
}
}
int main()
{
#ifdef ONLINE_JUDGE
#else
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
qc;
int T;
cin >> T;
while(T--){
solve();
}
return 0;
}