@bzoj


@description@

给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有 need 条白色边的生成树。
题目保证有解。

input
第一行V, E, need分别表示点数,边数和需要的白色边数。
接下来 E 行,每行 s, t, c, col 表示这边的端点(点从0开始标号),边权,颜色(0白色,1黑色)。
output
一行表示所求生成树的边权和。
V <= 50000, E<=100000, 所有数据边权为[1, 100]中的正整数。

sample Input
2 2 1
0 1 1 1
0 1 2 0
sample output
2

@solution@

dp 凸优化的起源……尽管这道题并不是 dp……
事实上这个算法有三个名字:WQS 二分,dp 凸优化,带权二分。
大家可以去看看这一位 dalao 的博客

@part - 1@

一个直观的想法是,我们先用 K 条白边生成一棵最小生成树,再把黑边加进去。
然而好像是能被卡掉的。所以我们不能这么搞。

那怎么去限制白边数量呢?我们给每条白边二分一个附加权值,再生成最小生成树,求出白边数量调整二分。
可以从直观上感性理解:假如这个附加权值越大,则我们选取的白边会越少,反之会越大。因此附加权值与白边数量的关系是单调的,我们就可以玩二分了。

最后从这个生成树中减去附加权值即可。

@part - 2@

然而这个算法……看起来并不是很严谨啊。
会不会出现一种情况:二分的附加权值稍稍大一点白边数量就不够,二分的附加权值稍稍小一点白边数量又多出来了。总之就是二分不到题目说的 K 条。
以及:这个附加权值可以为小数吗?我二分需要管它的精度问题吗?

好的。我们慢慢来。
先定义一个函数 (f(x)) 表示当我选取 (x) 条白边的时候,求得的最小生成树权值。
经过 dalao 们的打表严格证明,我们可以得到 (f(x)) 是下凸函数。
并且,我们也可以求出 (f(x)) 的最小值(原图中的最小生成树)。

好,我们再来看我们刚刚的算法干了什么。我们二分了一个附加权值 (k),并把所有白边都加上了这个附加权值,得到一个新函数 (g(x) = f(x)+k*x)
然后我们找到这个新函数的最小值(新图中的最小生成树)。求出它对应的横坐标(生成树所含的白边数量),并以此调整二分。

上面我所提到的那位 dalao 提出了一种理解该算法的方法:我们对 (f(x))(g(x)) 分别求导,得到 (f'(x))(g'(x))
因为 (f(x)) 是下凸的,所以 (f'(x)) 是单增的,同时 (f'(x)) 的零点对应着 (f(x)) 的最小值。
可以得到 (g'(x) = f'(x) + k),即将 (f'(x)) 平移 (k) 个单位。这样就可以说明该算法的正确性了。
同时,因为 (f(x)) 上的点都是整数,所以 (f'(x)) 上的点也应该都是整数,所以我们不需要进行小数二分。

@accepted code@

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int MAXN = 50000;
struct edge{
	int u, v, w;
	edge(int _u=0, int _v=0, int _w=0):u(_u), v(_v), w(_w){}
};
bool operator < (edge a, edge b) {
	return a.w < b.w;
} 
vector<edge>edges[2];
void init() {
	for(int i=0;i<2;i++)
		edges[i].clear();
}
int fa[MAXN + 5];
int find(int x) {
	return (fa[x] == x) ? x : fa[x] = find(fa[x]);
}
int N, M, K, ans, cnt, p, q;
bool check(int x) {
	ans = cnt = p = q = 0;
	for(int i=1;i<=N;i++)
		fa[i] = i;
	while( p < edges[0].size() && q < edges[1].size() ) {
		if( find(edges[0][p].u) == find(edges[0][p].v) )
			p++;
		else if( find(edges[1][q].u) == find(edges[1][q].v) )
			q++;
		else {
			if( edges[0][p].w + x < edges[1][q].w ) {
				ans += edges[0][p].w + x;
				fa[find(edges[0][p].u)] = find(edges[0][p].v);
				cnt++; p++;
			}
			else {
				ans += edges[1][q].w;
				fa[find(edges[1][q].u)] = find(edges[1][q].v);
				q++;
			}
		}
	}
	while( p < edges[0].size() ) {
		if( find(edges[0][p].u) == find(edges[0][p].v) )
			p++;
		else {
			ans += edges[0][p].w + x;
			fa[find(edges[0][p].u)] = find(edges[0][p].v);
			cnt++; p++;
		}
	}
	while( q < edges[1].size() ) {
		if( find(edges[1][q].u) == find(edges[1][q].v) )
			q++;
		else {
			ans += edges[1][q].w;
			fa[find(edges[1][q].u)] = find(edges[1][q].v);
			q++;
		}
	}
	return cnt <= K;
}
int main() {
	init();
	scanf("%d%d%d", &N, &M, &K);
	for(int i=1;i<=M;i++) {
		int a, b, c, x;
		scanf("%d%d%d%d", &a, &b, &c, &x);
		edges[x].push_back(edge(a + 1, b + 1, c));
	}
	for(int i=0;i<2;i++)
		sort(edges[i].begin(), edges[i].end());
	int le = -100, ri = 100;
	while( le < ri ) {
		int mid = (le + ri) >> 1;
		if( check(mid) ) ri = mid;
		else le = mid + 1;
	}
	check(le);
	printf("%d
", ans - K*le);
}

@details@

事实证明,这道题 bzoj 的数据非常的水。
我带权二分的时候连那个二分出来的附加权值都没有加到边权里去。
然后在 bzoj 上它竟然 AC 了?

建议大家如果要检测自己代码的正确性,去 hdu4253 (需要写多组数据)。
否则你连自己代码有错都不知道,一懵一懵地好像自己就 AC 了。

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/10214533.html