思路
官方题解写的很清楚,将原边看成白边,折扣边看成黑边,求恰好黑边数为(k)的最小生成树。
这边有道一模一样的子问题BZOJ2654-tree.
我们需要设定一个(x)值,对每条黑边的长度算上(+x)的贡献,然后进行一次最小生成树,假如最小生成树的最终值为(sum),选择了(cnt)条黑边,那么最小生成树的值就是(sum-cnt*x)。因为题目所给的边的范围为([0,1000]),所以(x)的范围为([-1000,1000]).这样可以保证每一种黑边的情况。
在计算之前,首先可以把图缩小到(o(n))的级别,假如没有黑边,就是一个所有白边的最小生成树,没有白边,就是一个所有黑边的最小生成树。这样只有(O(2n))条边,那么对于最终黑白边混合的最小生成树,其所选的边一定是从这(2n)条边中选择。
同时我们可以发现,当(x)值越小的时候,选择的黑边就会越多,满足单调性。
对于寻找边数恰好为(k)的最小生成树,就有以下做法:
1、二分一个(x),将所有的黑边的权值(+x)。
2、然后跑一遍最小生成树,记录最少需要黑边的数量(l_x),最多需要黑边的数量(r_x)
3、若对于一个(k)满足(l_xleq k leq r_x),那么此时得到的(sum-cnt*x)可以作为答案。
在二分过程中每次查看返回的黑边数量是否满足要求即可。
对于BZOJ2654-tree这题因为只是输出一个答案,所以直接二分求值即可。对于此题需要先预处理出(x)从(-1000)到(1000)的所有情况,然后再去(klogn)的复杂度查询即可,发现标程这边偷懒写了(kn)的写法。
代码
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
const int N = 1e3 + 10, M = 2e5 + 10;
struct Edge {
int u, v, w;
friend bool operator < (const Edge &a, const Edge &b) {
return a.w < b.w;
}
}e1[M], e2[M];
int fa[N];
int n, m;
pii f[N * 2];
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void init_edge() {
for(int i = 1; i <= n; i++) fa[i] = i;
sort(e1 + 1, e1 + 1 + m);
for(int i = 1, idx = 0; i <= m; i++) {
int fx = find(e1[i].u), fy = find(e1[i].v);
if(fx != fy) {
fa[fx] = fy;
e1[++idx] = e1[i];
}
}
for(int i = 1; i <= n; i++) fa[i] = i;
sort(e2 + 1, e2 + 1 + m);
for(int i = 1, idx = 0; i <= m; i++) {
int fx = find(e2[i].u), fy = find(e2[i].v);
if(fx != fy) {
fa[fx] = fy;
e2[++idx] = e2[i];
}
}
}
bool merge(int x, int y) {
int fx = find(x), fy = find(y);
if(fx == fy) return 0;
fa[fx] = fy;
return 1;
}
pii calc(int x) {
int l = 1, r = 1, cnt = 0, sum = 0;
for(int i = 1; i <= n; i++) fa[i] = i;
while(l < n && r < n) {
if(e1[l].w > e2[r].w + x) {
if(merge(e2[r].u, e2[r].v)) {
sum += e2[r].w + x;
cnt++;
}
r++;
}
else {
if(merge(e1[l].u, e1[l].v)) {
sum += e1[l].w;
}
l++;
}
}
while(l < n) {
if(merge(e1[l].u, e1[l].v)) {
sum += e1[l].v;
}
l++;
}
while(r < n) {
if(merge(e2[r].u, e2[r].v)) {
sum += e2[r].w + x;
cnt++;
}
r++;
}
return {sum, cnt};
}
int query(int x) {
int l = -1000, r = 1000;
int res = -1;
while(l <= r) {
int mid = l + r >> 1;
if(f[mid + 1000].second <= x) {
res = f[mid + 1000].first - x * mid;
r = mid - 1;
}
else l = mid + 1;
}
return res;
}
void solve() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++) {
int u, v, w1, w2;
scanf("%d%d%d%d", &u, &v, &w1, &w2);
e1[i] = {u, v, w1};
e2[i] = {u, v, w2};
}
init_edge();
for(int k = -1000; k <= 1000; k++) f[k + 1000] = calc(k);
for(int i = 0; i <= n - 1; i++) {
printf("%d
", query(i));
}
}
int main() {
int t; cin >> t; while(t--)
solve();
return 0;
}