洛谷 P5360 - [SDOI2019]世界地图(最小生成树+虚树)

洛谷题面传送门

好题。

首先看到抠掉一个区间的限制,我们很自然地想到对前后缀跑一遍 MST 后把左右两半的信息合并起来的想法,于是问题转化为怎样维护前后缀的最小生成树。

直接做复杂度 \(nm^2\log n\)​,显然无法通过,乍一眼貌似也需要可持久化 LCT / 树状数组套 LCT 等奇奇怪怪的数据结构才能优化,看上去异常棘手。但是别忘了,我们还没有用到“图是一张网格图”的性质。我们注意到,这题列数 \(m\)​ 很多但行数 \(n\)​ 很小,因此我们肯定尽量将复杂度倾向于 \(n\)​。可以发现当我们新增扩展一列 \(i\)​ 时,我们新增的边只会连在 \(\mathcal O(n)\)​ 个点之间,即所有形如 \((j,i-1),1\le j\le m\)​ 的点。按照 LCT 维护最小生成树的那套理论,当我们新加入一条边 \(E=(u,v,w)\)​ 时,最小生成树的变化可以表现为,取出 \(u,v\)​ 路径上权值最大的边 \(E_0\)​,如果 \(E_0\) 的权值 \(>w\) 则删除 \(E_0\) 加入 \(E\),否则就什么也不干。也就是说,在我们这一轮扩展中,只有这 \(n\) 个关键点两两路径上权值最大的边可能会在这一轮扩展中被删除,而这样的边最多只有 \(\mathcal O(n)\) 个,因为如果我们对 \(n\) 个关键点建虚树,那么这样的边都肯定虚树上某条链上权值最大的边,而虚树上边数最大为 \(2n-2\)。

因此我们考虑不记录整个最小生成树的边集,而只记录这些“关键边”组成的集合,对于剩余的在 MST 上的边,无论我们怎么扩展,它们肯定都会在 MST 上,因此我们只用单纯地记录一下它们的边权之和即可。直接记录这些边在原图中的编号则会导致 kruskal 的结果出错,因此我们不能直接记录这些边在原图上的编号,改进方法是,我们找出 \((1,1),(2,1),(3,1),\cdots,(n,1),(1,i-1),(2,i-1),\cdots,(n,i-1)\) 这些点在 \(1\sim i-1\) 上的虚树,然后对虚树上 \(\mathcal O(n)\) 个点重标号并对虚树上每条链求出权值最大的边,这样新增一列时,我们将新增的边与原来 \(\mathcal O(n)\) 条边放在一起跑 kruskal,建出这 \(9n\) 个点的最小生成树后再建出以 \((1,1),(2,1),\cdots,(n,1),(1,i),(2,i),\cdots,(n,i)\) 为关键点的虚树,求出每条链上权值最大的边作为新的关键边集合即可实现合并两棵 MST 的过程。

时间复杂度 \(n(m+q)\log n\)​,部分不清楚的地方可通过阅读代码理解。

const int MAXN = 100;
const int MAXM = 1e4;
const int MAXC = MAXN << 4;
int n, m, hor[MAXN + 5][MAXM + 5], vert[MAXN + 5][MAXM + 5];
u32 SA, SB, SC; int lim;
int getweight() {
	SA ^= SA << 16; SA ^= SA >> 5; SA ^= SA << 1;
	unsigned int t = SA;
	SA = SB; SB = SC; SC ^= t ^ SA;
	return SC % lim + 1;
}
struct edge {
	int u, v, w;
	edge(int _u = 0, int _v = 0, int _w = 0): u(_u), v(_v), w(_w) {}
	bool operator < (const edge &rhs) {return w < rhs.w;}
};
struct dsu {
	int f[MAXC + 5];
	void init() {memset(f, 0, sizeof(f));}
	int find(int x) {return (!f[x]) ? x : f[x] = find(f[x]);}
	bool merge(int x, int y) {x = find(x); y = find(y); return (x == y) ? 0 : (f[x] = y, 1);}
} F;
struct graph {
	int hd[MAXC + 5], nxt[MAXC * 2 + 5], to[MAXC * 2 + 5], val[MAXC * 2 + 5], ec = 0;
	void init() {memset(hd, 0, sizeof(hd)); ec = 0;}
	void adde(int u, int v, int w) {
		to[++ec] = v; val[ec] = w; nxt[ec] = hd[u]; hd[u] = ec;
		to[++ec] = u; val[ec] = w; nxt[ec] = hd[v]; hd[v] = ec;
	}
} G;
bool is[MAXC + 5], ont[MAXC + 5];
struct MST {
	vector<edge> E; int tot;
	ll static_sum; // sum of static edges
	void init(vector<int> w) {
		tot = n;
		for (int i = 0; i < w.size(); i++)
			E.pb(edge(i + 1, i + 2, w[i]));
	}
	ll query() {
		ll sum = static_sum;
		for (int i = 0; i < E.size(); i++) sum += E[i].w;
		return sum;
	}
} pre[MAXM + 5], suf[MAXM + 5];
int fa[MAXC + 5], faw[MAXC + 5];
void dfs_init(int x, int f) {
	fa[x] = f;
	for (int e = G.hd[x]; e; e = G.nxt[e]) {
		int y = G.to[e], z = G.val[e]; if (y == f) continue;
		faw[y] = z; dfs_init(y, x);
	}
}
int id[MAXC + 5], idcnt = 0;
vector<pii> te;
int dfs_build(int x, int f) {
	int V = 0, two = 0; ont[x] = is[x];
	for (int e = G.hd[x]; e; e = G.nxt[e]) {
		int y = G.to[e]; if (y == f) continue;
		int z = dfs_build(y, x);
		if (z) {
			if (V) two = 1, te.pb(mp(x, z));
			else V = z;
		}
	}
	if (!V) return (is[x]) ? x : 0;
	else {
		ont[x] = 1;
		if (two) return is[x] = 1, te.pb(mp(x, V)), x;
		else {
			if (is[x]) return te.pb(mp(x, V)), x;
			else return V;
		}
	}
}
int qrymx(int u, int v) {
	int mx = 0;
	while (v ^ u) chkmax(mx, faw[v]), v = fa[v];
	return mx;
}
MST merge(MST &a, MST &b, vector<int> w) {
	vector<edge> ve;
	MST c; c.tot = a.tot + b.tot;
	for (int i = 0; i < a.E.size(); i++) ve.pb(a.E[i]);
	for (int i = 0; i < b.E.size(); i++) ve.pb(edge(b.E[i].u + a.tot, b.E[i].v + a.tot, b.E[i].w));
	for (int i = 1; i <= n; i++) ve.pb(edge(a.tot - n + i, a.tot + i, w[i - 1]));
	F.init(); G.init(); ll esum = 0; sort(ve.begin(), ve.end());
	for (int i = 0; i < ve.size(); i++) if (F.merge(ve[i].u, ve[i].v))
		G.adde(ve[i].u, ve[i].v, ve[i].w), esum += ve[i].w;
	memset(is, 0, sizeof(is)); memset(ont, 0, sizeof(ont));
	for (int i = 1; i <= c.tot; i++) is[i] = (i <= n || i > c.tot - n);
	dfs_init(1, 0); te.clear(); dfs_build(1, 0);
	idcnt = 0; for (int i = 1; i <= c.tot; i++) if (ont[i]) id[i] = ++idcnt;
	for (pii p : te) c.E.pb(edge(id[p.fi], id[p.se], qrymx(p.fi, p.se)));
	for (int i = 0; i < c.E.size(); i++) esum -= c.E[i].w;
	c.static_sum = a.static_sum + b.static_sum + esum;
	c.tot = idcnt;
	return c;
}
int main() {
	scanf("%d%d%u%u%u%d", &n, &m, &SA, &SB, &SC, &lim);
	for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) hor[i][j] = getweight();
	for (int i = 1; i < n; i++) for (int j = 1; j <= m; j++) vert[i][j] = getweight();
//	printf("hor:\n");
//	for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++)
//		printf("%d%c", hor[i][j], " \n"[j == m]);
//	printf("vert:\n");
//	for (int i = 1; i < n; i++) for (int j = 1; j <= m; j++)
//		printf("%d%c", vert[i][j], " \n"[j == m]);
	for (int j = 1; j <= m; j++) {
		vector<int> vec;
		for (int i = 1; i < n; i++) vec.pb(vert[i][j]);
		pre[j].init(vec); suf[j].init(vec);
	}
	for (int i = 2; i <= m; i++) {
		vector<int> vec;
		for (int j = 1; j <= n; j++) vec.pb(hor[j][i - 1]);
		pre[i] = merge(pre[i - 1], pre[i], vec);
	}
	for (int i = m - 1; i; i--) {
		vector<int> vec;
		for (int j = 1; j <= n; j++) vec.pb(hor[j][i]);
		suf[i] = merge(suf[i], suf[i + 1], vec);
	}
//	printf("pre:\n");
//	for (int i = 1; i <= m; i++) {
//		printf("[1, %d]:\n", i);
//		for (int j = 0; j < pre[i].E.size(); j++)
//			printf("%d %d %d\n", pre[i].E[j].u, pre[i].E[j].v, pre[i].E[j].w);
//		printf("weight of MST: %lld\n", pre[i].query());
//	}
	int qu; scanf("%d", &qu);
	while (qu--) {
		int l, r; scanf("%d%d", &l, &r); vector<int> vec;
		for (int j = 1; j <= n; j++) vec.pb(hor[j][m]);
		printf("%lld\n", merge(suf[r + 1], pre[l - 1], vec).query());
	}
	return 0;
}
/*
6 5 998244353 1004535809 1000000007 5
6
2 2
2 3
2 4
3 3
3 4
4 4
*/
上一篇:django+vue项目跨域问题


下一篇:Python配置Django+MySQL环境