【LG3206】[HNOI2010]城市建设

【LG3206】[HNOI2010]城市建设

题面

洛谷

题解

有一种又好想、码得又舒服的做法叫线段树分治+\(LCT\)

但是因为常数过大,无法跑过此题。

所以这里主要介绍另外一种玄学\(cdq\)分治

对时间进行分治

因为每次分治都必须要缩小数据规模

而我们这里貌似无法满足这个要求

引进了下面的玄学东西:

设当前边集的大小为\(n\),分治区间为\([l,r]\)

则对于分治区间内的边,我们有如下两种剪枝:

\((1)Contraction:\)
将现在所有分治区间内的边权设为\(-\infty\),做一遍最小生成树
那么我们在最小生成树里面的除开边权为\(-\infty\)的边都要选,称这些边为必选边
\((2)Reduction:\)
将现在所有分治区间内的边设为\(\infty\),做一遍最小生成树,
那么此时没有出现在最小生成树中间的边一定不会选到,称为无用边

那么我们每次的无用边、必选边就无需考虑了

我们再用\(cdq\)分治修改序列,就可以达到减小数据规模的目的啦

复杂度网上说是\(O(nlog^2)\),但是不会证啊。

代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring> 
#include <cmath> 
#include <algorithm>
using namespace std; 
inline int gi() {
	register int data = 0, w = 1;
	register char ch = 0;
	while (!isdigit(ch) && ch != '-') ch = getchar(); 
	if (ch == '-') w = -1, ch = getchar();
	while (isdigit(ch)) data = 10 * data + ch - '0', ch = getchar();
	return w * data; 
} 
typedef long long ll;
const int INF = 1e9; 
const int MAX_N = 5e4 + 5;
struct Mdy { int x, y; } p[MAX_N]; 
struct Edge { int u, v, w, id; } e[18][MAX_N], tmp[MAX_N], stk[MAX_N]; 
inline bool operator < (const Edge &l, const Edge &r) { return l.w < r.w; } 
int N, M, Q, sum[18]; 
int a[MAX_N], c[MAX_N], fa[MAX_N], rnk[MAX_N]; 
ll ans[MAX_N]; 
int getf(int x) { return (x == fa[x]) ? x : fa[x] = getf(fa[x]); }
void unite(int x, int y) { 
	x = getf(x), y = getf(y); 
	if (x == y) return ; 
	if (rnk[x] != rnk[y]) (rnk[x] > rnk[y]) ? fa[y] = x : fa[x] = y; 
	else fa[x] = y, rnk[y]++; 
} 
void Set(int n, Edge *a) { 
	for (int i = 1; i <= n; i++) {
		fa[a[i].u] = a[i].u; 
		fa[a[i].v] = a[i].v; 
		rnk[a[i].v] = rnk[a[i].u] = 1; 
	} 
} 
void Contraction(int &n, ll &val) { 
	int top = 0; 
	Set(n, tmp); sort(&tmp[1], &tmp[n + 1]); 
	for (int i = 1; i <= n; i++) 
		if (getf(tmp[i].u) ^ getf(tmp[i].v)) 
			unite(tmp[i].u, tmp[i].v), stk[++top] = tmp[i]; 
	Set(top, stk); 
	for (int i = 1; i <= top; i++) 
		if (stk[i].w != -INF && getf(stk[i].u) ^ getf(stk[i].v)) 
			val += stk[i].w, unite(stk[i].u, stk[i].v); 
	top = 0; 
	for (int i = 1; i <= n; i++) 
		if (getf(tmp[i].u) ^ getf(tmp[i].v)) 
	 		stk[++top] = (Edge){getf(tmp[i].u), getf(tmp[i].v), tmp[i].w, tmp[i].id}; 
	for (int i = 1; i <= top; i++) c[tmp[i].id] = i, tmp[i] = stk[i]; 
	n = top; 
} 
void Reduction(int &n) { 
	int top = 0; 
	Set(n, tmp); sort(&tmp[1], &tmp[n + 1]); 
	for (int i = 1; i <= n; i++)
		if (getf(tmp[i].u) ^ getf(tmp[i].v)) 
			unite(tmp[i].u, tmp[i].v), stk[++top] = tmp[i]; 
		else if (tmp[i].w == INF) stk[++top] = tmp[i]; 
	for (int i = 1; i <= top; i++) c[tmp[i].id] = i, tmp[i] = stk[i]; 
	n = top; 
}
void Div(int l, int r, int dep, ll val) {
	int n = sum[dep]; 
	if (l == r) a[p[l].x] = p[l].y; 
	for (int i = 1; i <= n; i++) {
		e[dep][i].w = a[e[dep][i].id]; 
		tmp[i] = e[dep][i], c[tmp[i].id] = i; 
	} 
	if (l == r) {
		ans[l] = val, Set(n, tmp); 
		sort(&tmp[1], &tmp[n + 1]);
		for (int i = 1; i <= n; i++) 
			if (getf(tmp[i].u) != getf(tmp[i].v))
				unite(tmp[i].u, tmp[i].v), ans[l] += tmp[i].w; 
		return ; 
	} 
	for (int i = l; i <= r; i++) tmp[c[p[i].x]].w = -INF; 
	Contraction(n, val); 
	for (int i = l; i <= r; i++) tmp[c[p[i].x]].w = INF; 
	Reduction(n); 
	for (int i = 1; i <= n; i++) e[dep + 1][i] = tmp[i]; 
	sum[dep + 1] = n;
	int mid = (l + r) >> 1; 
	Div(l, mid, dep + 1, val); 
	Div(mid + 1, r, dep + 1, val); 
} 
int main () {
	N = gi(), M = gi(), Q = gi(); 
	for (int i = 1; i <= M; i++) e[0][i] = (Edge){gi(), gi(), a[i] = gi(), i}; 
	for (int i = 1; i <= Q; i++) p[i] = (Mdy){gi(), gi()};
    sum[0] = M; Div(1, Q, 0, 0); 
	for (int i = 1; i <= Q; i++) printf("%lld\n", ans[i]); 
	return 0; 
} 
posted @ 2019-01-29 14:31  heyujun  阅读(472)  评论(0编辑  收藏  举报