2021杭电多校第三场-Road Discount-wqs二分+最小生成树
Description
There are n cities in Byteland, labeled by 1 to n. The Transport Construction Authority of Byteland is planning to construct n−1 bidirectional roads among these cities such that every pair of different cities are connected by these roads directly or indirectly.
The engineering company has offered m possible candidate roads to construct. The i-th candidate road will cost ci dollars, and if it is finally constructed, there will be a road connecting the ui-th city and the vi-th city directly. Fortunately, each road has its discounted price, the i-th of which is di.
The Transport Construction Authority of Byteland can buy at most k roads at their discounted prices. Please write a program to help the Transport Construction Authority find the cheapest solution for k=0,1,2,…,n−1.
Input
The first line contains a single integer T (1≤T≤10), the number of test cases. For each test case:
The first line contains two integers n and m (2≤n≤1000, n−1≤m≤200000), denoting the number of cities and the number of candidate roads.
Each of the following m lines contains four integers ui,vi,ci and di (1≤ui,vi≤n, ui≠vi, 1≤di≤ci≤1000), describing a candidate road.
Output
For each test case, output n lines, the i-th (1≤i≤n) of which containing an integer, denoting the cheapest total cost to construct n−1 roads when k=i−1.
It is guaranteed that the answer always exists.
Samples
Input Copy
1
5 6
1 2 1 1
2 3 2 1
2 4 3 2
2 5 4 3
1 3 5 3
4 5 6 1
Output
10
7
6
5
5
给出一个n个点的图,有m条边,每条边有两个价值属性,分别是原始价值和折扣价值,问至少含有i(1 <= i <= n) 条折扣边的最小生成树
官方题解:
:::
将原始价格的边看作是白边,将折扣价格的边看作是黑边,对于每一个输入,黑边和白边连的都是同样的两个端点,所以说同一条边是不可能被选择两次的,所以说问题就相当于是求含有k条折扣边(黑边)的最小生成树问题
设f[x] 是正好包含x条黑边的最小生成树的权值之和,然后这个函数一定是一个上凸的函数,其实这里有wqs二分
的意思
wqs是对一个上(上/下)凸的函数二分斜率求出最优解的过程,本题可以说是用到了其思想/doge
考虑两种极端的情况,全选择白边(选择0条黑边)和全选择黑边的两种情形,此时我们对上述情况分别求最小生成树,然后重构输入的两种边,因为我们在求最小生成树的过程当中,将没有用到的非树边去掉是不会影响答案的,将没有用到的非树边去掉也是可以减去一部分复杂度的,经过这样的操作,白边和黑边只会剩下n-1
条
然后就很轻松的解决本题啦
#define PII pair<int, int>
int fa[maxn];
int n, m;
PII save[2007];
struct node {
int u, v, w;
friend bool operator<(node a, node b) {
return a.w < b.w;
}
} a[maxn], b[maxn];
int find(int x) {
if (x == fa[x])
return x;
else
return fa[x] = find(fa[x]);
}
bool Union(int x, int y) {
int fax = find(x);
int fay = find(y);
if (fax == fay) return false;
fa[fax] = fay;
return true;
}
PII get(int x) {
for (int i = 1; i <= n; i++) fa[i] = i;
int A = 1, B = 1;
int tot = 0, black = 0;
while (A < n && B < n) {
if (a[A].w <= b[B].w + x) {
if (Union(a[A].u, a[A].v)) tot += a[A].w;
++A;
} else {
if (Union(b[B].u, b[B].v)) tot += b[B].w + x, black++;
++B;
}
}
while (A < n) {
if (Union(a[A].u, a[A].v)) tot += a[A].w;
++A;
}
while (B < n) {
if (Union(b[B].u, b[B].v)) tot += b[B].w + x, black++;
++B;
}
return PII{tot, black};
}
int main() {
int _ = read;
while (_--) {
n = read, m = read;
// memset(save,-1,sizeof save);
for (int i = 1; i <= m; i++) {
b[i].u = a[i].u = read;
b[i].v = a[i].v = read;
a[i].w = read;
b[i].w = read;
}
sort(a + 1, a + 1 + m);
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 1, t = 0; i <= m; i++) {
if (Union(a[i].u, a[i].v)) a[++t] = a[i];
}
sort(b + 1, b + 1 + m);
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 1, t = 0; i <= m; i++) {
if (Union(b[i].u, b[i].v)) b[++t] = b[i];
}
for (int i = -1000; i <= 1000; i++) {
save[1000 + i] = get(i);
}
// debug(save[1000].first);
// debug(save[1000].second);
for (int i = 0; i < n; i++) {
int flag = 0;
for (int j = -1000; j <= 1000; j++) {
if (save[j + 1000].second <= i) {
printf("%d\n", save[1000 + j].first - i * j);
flag = 1;
break;
}
}
if (!flag) puts("-1");
}
}
return 0;
}
代码意思讲解:
bool Union(int x, int y) {
int fax = find(x);
int fay = find(y);
if (fax == fay) return false;
fa[fax] = fay;
return true;
}
如果两个点在求最小生成树的过程中用到了,那么就返回true,否则返回false
get
函数是求出将黑色的边权加上一个值x之后的一个花费,我们会这个函数处理出x=-1000->1000的所有情况,然后将信息储存在save中,然后在询问的时候,直接遍历save集合,遇见满足情况的便直接输出,否则输出-1,虽然没有-1的情况/doge