1. 程式人生 > 其它 >洛谷 P5360 - [SDOI2019]世界地圖(最小生成樹+虛樹)

洛谷 P5360 - [SDOI2019]世界地圖(最小生成樹+虛樹)

利用網格圖的性質 + 虛樹高效解決網格圖前後綴 MST 問題

洛谷題面傳送門

好題。

首先看到摳掉一個區間的限制,我們很自然地想到對前後綴跑一遍 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
*/