[POI2008] POC-Trains 題解

XuYueming發表於2024-07-30

前言

題目連結:洛谷

時間複雜度和輸入同階的做法。

題意簡述

\(n\)\(n \leq 10^3\))個長 \(m\) 的字串,\(q\)\(q \leq 10^5\))次操作,交換兩個字串的兩個字元。問每個字串在所有時刻,最多有幾個和它相等。

題目分析

套路做法

看到字串相等,想到使用雜湊。但是要支援修改,怎麼辦呢?上資料結構!當然不是,把雜湊的結果看做一個數字,每次把某一位的值扣掉再加上就行了。即減去該位權值乘上原值,加上權值乘上修改後的值。

至於詢問,每次操作後不可能 \(\Theta(n^2)\) 地弄。發現只會和當前修改的兩個串有關,故 \(\Theta(n)\) 把這兩個串的結果統計一遍。其他的串,維護一個 \(cur_i\) 表示當前 \(i\) 能和幾個字串相等。如果 \(i\) 和修改之前的串相等,就減去,如果和修改後的串相等了,再加上,最後和答案取個 \(\max\) 即可。

時間複雜度 \(\Theta(nm + nq)\)。可過此題。當然,可以最佳化。

最佳化做法

嘗試從雜湊角度分析。發現,一個字串不斷從一個相同雜湊值得集合中,移動到另一個集合中,而答案就是這段時間內,該集合大小的最大值。如果考慮在刪除的時候統計答案,就是在該元素加入集合後,該集合大小的最大值。

嘗試把集合大小獨立出來,看做單獨的一個權值。這樣就可以把刪除這個操作去掉。即我們可以把元素擴充套件,額外附加它在加入集合時集合的大小,並不做刪除,而是直接將集合大小減一。這樣查詢,就成了一個字尾最大值。

對於一條鏈,我們會掃過很多次,不妨從這裡最佳化,即去除重複操作。想到,如果掃過了一個值,那下次不用再從原來的位置開始再掃一遍,而是,把當前掃過來的最值記下來,下次直接從這裡開始掃就行了。很類似並查集的路徑壓縮。

時間複雜度呢?每條邊只會被經過一遍,和 \(n + q\) 是同階的。總的時間複雜度是 \(\Theta(nm + q)\) 的,非常快。

程式碼

naive code

程式碼

最佳化

雜湊值域太小會 WA,所以使用了 unordered_map,對時間複雜度沒有影響。略去了快讀,是最優解

#include <cstdio>
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;

const int N = 1010;
const int M = 110;
const int Q = 100010;
const int mod = 1e9 + 11;
const int bas = 1331;

inline int add(int a, int b) {
	return a + b >= mod ? a + b - mod : a + b;
}

inline int sub(int a, int b) {
	return a - b < 0 ? a - b + mod : a - b;
}

inline int mul(int a, int b) {
	return 1ll * a * b % mod;
}

int n, m, q;
char str[N][M];
int hsh[N], res[N], pw[M];
int whr[N];

struct node {
	int fa, ans;
} tree[N + Q * 2];
int tot;

unordered_map<int, int> ysiz, ylst;

int query(int x) {
	if (tree[x].fa == x) return tree[x].ans;
	int res = query(tree[x].fa);
	tree[x].fa = tree[tree[x].fa].fa;
	return tree[x].ans = max(tree[x].ans, res);
}

void update(int x) {
	int bl = hsh[x];
	whr[x] = ++tot, ++ysiz[bl];
	tree[tot] = {tot, ysiz[bl]};
	tree[ylst[bl]].fa = tot;
	ylst[bl] = tot;
}

signed main() {
	fread(buf, 1, MAX, stdin);
	read(n), read(m), read(q);
	pw[0] = 1;
	for (int i = 1; i <= m; ++i)
		pw[i] = mul(pw[i - 1], bas);
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			hsh[i] = add(mul(hsh[i], bas), str[i][j] = readchar());
		}
		update(i);
	}
	for (int i = 1, a, x, b, y; i <= q; ++i) {
		read(a), read(x), read(b), read(y);
		res[a] = max(res[a], query(whr[a])), --ysiz[hsh[a]];
		if (a != b) res[b] = max(res[b], query(whr[b])), --ysiz[hsh[b]];
		hsh[a] = sub(hsh[a], mul(pw[m - x], str[a][x]));
		hsh[b] = sub(hsh[b], mul(pw[m - y], str[b][y]));
		swap(str[a][x], str[b][y]);
		hsh[a] = add(hsh[a], mul(pw[m - x], str[a][x]));
		hsh[b] = add(hsh[b], mul(pw[m - y], str[b][y]));
		update(a);
		if (a != b) update(b);
	}
	for (int i = 1; i <= n; ++i) {
		printf("%d\n", max(res[i], query(whr[i])));
	}
	return 0;
}