The 2nd Universal Cup. Stage 11: Nanjing【杂题】

比赛链接

A. Cool, It’s Yesterday Four Times More

容易证明如下结论:同一连通块内袋鼠的输赢情况相同。枚举连通块暴力就行了。时间复杂度 \(\mathcal{O}(n^2m^2)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <int, int> pi;
#define fi first
#define se second
constexpr int N = 1e3 + 5;
const int dx[4] = {1, 0, -1, 0};
const int dy[4] = {0, 1, 0, -1}; 
bool Mbe;
int n, m, vis[N][N];
char a[N][N];
void solve() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> a[i] + 1;
		for (int j = 1; j <= m; j++) vis[i][j] = 0;
	}
	auto chk = [&](int x, int y) {
		return x >= 1 && x <= n && y >= 1 && y <= m && a[x][y] == '.';
	};
	auto bfs = [&](int sx, int sy) {
		vector <pi> res;
		queue <pi> q;
		q.push(make_pair(sx, sy));
		vis[sx][sy] = 1;
		while (!q.empty()) {
			auto it = q.front();
			int x = it.fi;
			int y = it.se;
			q.pop();
			res.emplace_back(x - sx, y - sy);
			for (int k = 0; k < 4; k++) {
				int nx = x + dx[k];
				int ny = y + dy[k];
				if (chk(nx, ny) && !vis[nx][ny]) {
					vis[nx][ny] = 1;
					q.push(make_pair(nx, ny));
				}
			}
		}
//		cout << "-------\n";
//		cout << "sx = " << sx << ", sy = " << sy << "\n";
//		for (auto [x, y] : res) cout << x << " " << y << "\n";
		return res;
	};
	int ans = 0;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++) 
			if (a[i][j] == '.' && !vis[i][j]) {
				auto tmp = bfs(i, j);
				bool ok = true;
				for (int x = 1; x <= n; x++) 
					for (int y = 1; y <= m; y++) {
						if (x == i && y == j) continue;
						bool flag = false;
						for (auto [dx, dy] : tmp) 
							if (!chk(x + dx, y + dy)) {
								flag = true;
								break;
							}
						if (!flag) {
							ok = false;
							goto end;
						}
					}
				end :
				if (ok) ans += tmp.size();
			}
	cout << ans << "\n";
}
bool Med;
int main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t; cin >> t;
	while (t--) solve();
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}
/*
4
2 5
.OO..
O..O.

1 3
O.O

1 3
.O.

2 3
OOO
OOO
*/

C. Primitive Root

\([0,m]\) 区间内的数异或 \(x\) 之后会被划分成 \(\mathcal{O}(\log m)\) 段区间,对每段区间统计有多少形如 \(kP + 1\) 的数即可。

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <int, int> pi;
#define fi first
#define se second
constexpr int N = 1e5 + 5;
bool Mbe;
LL p, m;
void solve() {
	cin >> p >> m;
	LL ans = 0;
	auto cnt = [&](LL x) {
		return x / p + (x % p >= 1);
	};
	auto calc = [&](LL l, LL r) {
		return cnt(r) - cnt(l - 1);
	};
	LL x = p - 1;
	LL curm = 0, curx = 0;
	for (int i = 60; i >= 0; i--) {
		if (x & (1LL << i)) curx += 1LL << i;
		if (m & (1LL << i)) {
			LL cur = curm ^ curx;
			ans += calc(cur, cur + (1LL << i) - 1);
			curm += 1LL << i;
		}
	}
	if ((curm ^ x) % p == 1 && curm <= m) ans += 1;
	cout << ans << "\n";
}
bool Med;
int main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t; cin >> t;
	while (t--) solve();
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}
/*
3
2 0
7 11
1145141 998244353
*/

D. Red Black Tree

\(f(u, x)\) 表示以 \(u\) 为根的子树满足红黑树性质,且从 \(u\) 到任意后代叶子节点的路径上都有 \(x\) 个黑色点需要的最少修改次数。转移可以写成卷积形式,于是可以归纳证明 \(f(u,x)\) 关于 \(x\) 下凸。

一种做法是直接维护差分数组。另一种做法可以考虑做一些简单优化,首先每次可以启发式合并并且只需要枚举到最浅子树大小,然后一整条链可以一起转移,需要求端点单调的区间 \(\min\),可以直接单调队列,也可以利用下凸的性质 \(\mathcal{O}(1)\) 查询。复杂度不太会证,但是据说是 \(\mathcal{O}(n)\) 的,那就是吧。

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <int, int> pi;
#define fi first
#define se second
constexpr int N = 1e6 + 5, inf = 1e9;
bool Mbe;
int n, a[N], fa[N], sum[N], dep[N], len[N], ed[N], ns[N];
char s[N];
vector <int> e[N];
vector <int> f[N];
void chkmn(int &x, int y) {
	x = min(x, y);
}
void dfs(int u, int ff) {
	sum[u] = sum[ff] + a[u];
	dep[u] = dep[ff] + 1;
	len[u] = n + 1;
	for (auto v : e[u]) {
		dfs(v, u);
		chkmn(len[u], len[v]);
	}
	if (len[u] == n + 1) len[u] = 0;
	len[u] += 1;
	ed[u] = u;
	if (e[u].size() == 1) {
		ed[u] = ed[e[u][0]];
	}
}
void calc(int u) {
	if (!e[u].size()) {
		if (a[u]) {
			f[u].emplace_back(1);
			f[u].emplace_back(0);
		} else {
			f[u].emplace_back(0);
			f[u].emplace_back(1);
		}
		ns[u] = 0;
		return;
	}
	for (auto v : e[u]) calc(v);
	if (e[u].size() >= 2) {
		int son = e[u][0];
		swap(f[u], f[son]);
		f[u].resize(len[u]);
		for (auto v : e[u]) {
			if (v == son) continue;
			for (int i = 0; i < len[u]; i++) {
				f[u][i] += f[v][i];
			}
		}
		f[u].emplace_back(inf);
		static int tmp[N];
		if (a[u]) {
			for (int i = 0; i <= len[u]; i++) {
				tmp[i] = f[u][i] + 1;
				if (i) chkmn(tmp[i], f[u][i - 1]);
			}
		} else {
			for (int i = 0; i <= len[u]; i++) {
				tmp[i] = f[u][i];
				if (i) chkmn(tmp[i], f[u][i - 1] + 1);
			}
		}
		ns[u] = inf;
		for (int i = 0; i <= len[u]; i++) {
			f[u][i] = tmp[i];
			chkmn(ns[u], f[u][i]);
		}
	} else if (e[u].size() == 1 && (e[fa[u]].size() >= 2 || u == 1)) {
		int v = ed[u];
		if (f[v].size() != len[v] + 1) while (1);
		static int g[N][2], t[2];
		auto upd = [&](int &x) {
			x = max(x, 0);
			x = min(x, len[v]);
		};
		auto qry = [&](int k, int l, int r) {
			if (l > len[v] || r < 0) return inf;
			upd(l);
			upd(r);
			assert(l <= r);
			int res = 0;
			int p = t[k];
			if (r < p) {
				res = g[r][k];
			} else if (l <= p && r >= p) {
				res = g[p][k]; 
			} else {
				assert(l > p);
				res = g[l][k];
			}
			return res;
		};
		for (int i = 0; i <= len[v]; i++) {
			g[i][0] = f[v][i] + i;
			g[i][1] = f[v][i] - i;
		}
		for (int k = 0; k < 2; k++) {
			t[k] = 0;
			for (int i = 0; i <= len[v]; i++) {
				if (g[t[k]][k] > g[i][k]) t[k] = i;
			}
		}
		int cnt = dep[v] - dep[u];
		int d = sum[fa[v]] - sum[fa[u]];
		f[u].resize(len[u] + 1);
		for (int i = 0; i <= len[u]; i++) {
			int v0 = qry(0, i - d, i) + d - i;
			int v1 = qry(1, i - cnt, i - d) + i - d;
			f[u][i] = min(v0, v1);
		}	
		ns[u] = inf;
		for (int i = 0; i <= len[u]; i++) {
			chkmn(ns[u], f[u][i]);
		}
		assert(ns[u] == ns[v]);
		int tmp = v;
		while (true) {
			ns[tmp] = ns[v];
			tmp = fa[tmp];
			if (tmp == u) break;
		}
	} 
}
void solve() {
	cin >> n;
	cin >> s + 1;
	for (int i = 1; i <= n; i++) {
		a[i] = s[i] - '0';
	}
	for (int i = 2; i <= n; i++) {
		cin >> fa[i];
		e[fa[i]].emplace_back(i);
	}
	dfs(1, 0);
	calc(1);
	for (int i = 1; i <= n; i++) {
		cout << ns[i] << " \n"[i == n];
	}
	for (int i = 1; i <= n; i++) {
		e[i].clear();
		f[i].clear();
		sum[i] = dep[i] = len[i] = ed[i] = ns[i] = 0;
	}
}
bool Med;
int main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t; cin >> t;
	while (t--) solve();
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}
/*
2

9
101011110
1 1 3 3 3 6 2 2

4
1011
1 1 3
*/

E. Extending Distance

转成对偶图上的最小割。将原图中的边费用设为 \(0\),流量设为边权,再对每条边建费用为 \(1\),流量为 \(+∞\) 的一条额外边,问题就转换成了找流量为 \(D + K\) 的费用最小的流,其中 \(D\) 是原图的最短路。

原图的边权很大,而 \(K\) 很小,流量为 \(D + K\) 的流中有 \(D\) 的流费用都为 \(0\),我们可以先用 Dinic 跑最大流,把费用为 \(0\) 的流完,然后再拿普通费用流跑剩下的流。输出方案只需要费用为 \(1\) 的所有边的反向弧的流量,就能知道每条边操作了多少次。

时间复杂度为 \(\mathcal{O}(n^2m^2K)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <int, int> pi;
#define fi first
#define se second
constexpr int N = 5e3 + 5, M = 5e5 + 5, inf = 2e9 + 1234;
constexpr LL inff = 1e18;
bool Mbe;
struct E {
	int to, flow, cost, nxt;
	void clr() {
		to = flow = cost = nxt = 0;
	}
} e[M];
int tot = 1, hd[N];
void add_edge(int u, int v, int w, int c) {
	e[++tot] = { v, w, c, hd[u] }, hd[u] = tot;
}
void add(int u, int v, int w, int c = 0) {
	add_edge(u, v, w, c);
	add_edge(v, u, 0, -c);
}   
int n, m, k, s, t, c[N][N], d[N][N];
int id(int x, int y) {
	return (x - 1) * (m - 1) + y;
}
int dep[N], cur[N];
bool bfs() {
	memset(dep, -1, sizeof(dep));
	dep[s] = 0;
	cur[s] = hd[s];
	queue <int> q;
	q.push(s);
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		for (int i = hd[u]; i; i = e[i].nxt) {
			int v = e[i].to;
			if (dep[v] != -1 || e[i].flow == 0) continue;
			dep[v] = dep[u] + 1;
			cur[v] = hd[v];
			if (v == t) return true;
			q.push(v);		
		}
	}
	return false;
}
LL dfs(int u, LL flow) {
	if (u == t) return flow;
	LL res = 0;
	for (int i = cur[u]; i; i = e[i].nxt) {
		if (!flow) break;
		cur[u] = i;
		int v = e[i].to;
		if (dep[v] != dep[u] + 1 || !e[i].flow) continue;
		LL x = dfs(v, min(1LL * e[i].flow, flow));
		e[i].flow -= x;
		e[i ^ 1].flow += x;
		flow -= x;
		res += x;
	} 
	return res;
}
int dis[N], pre[N], val[N], eid[N]; bool vis[N];
bool spfa() {
	queue <int> q;
	memset(dis, 0x3f, sizeof(dis));
	dis[s] = 0;
	q.push(s);
	val[s] = inf;
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		vis[u] = 0;
		for (int i = hd[u]; i; i = e[i].nxt) {
			int v = e[i].to;
			if (e[i].flow && dis[v] > dis[u] + e[i].cost) {
				dis[v] = dis[u] + e[i].cost;
				val[v] = min(val[u], e[i].flow);
				pre[v] = u;
				eid[v] = i;
				if (!vis[v]) {
					vis[v] = true;
					q.push(v);
				}
			}
		}
	}
	return dis[t] != dis[0];
}
LL mcmf(int k) {
	LL res = 0;
	while (k) {
		spfa();
		val[t] = min(val[t], k);
		k -= val[t];
		res += 1LL * val[t] * dis[t];
		int x = t;
		while (x != s) {
			e[eid[x]].flow -= val[t];
			e[eid[x] ^ 1].flow += val[t];
			x = pre[x];
		} 
	}
	return res;
}
void solve() {
	cin >> n >> m >> k;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j < m; j++) {
			int x;
			cin >> x;
			c[i][j] = x;
			add(id(i, j), id(i + 1, j), x);
			add(id(i + 1, j), id(i, j), x);
		}
	for (int i = 2; i <= n; i++)
		for (int j = 0; j < m; j++) {
			int x;
			cin >> x;
			d[i][j] = x;
			if (j == 0 || j == m - 1) continue;
			add(id(i, j), id(i, j + 1), x);
			add(id(i, j + 1), id(i, j), x);
		}
	int L = tot + 1;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j < m; j++) {
			add(id(i, j), id(i + 1, j), 0, 1);
			add(id(i + 1, j), id(i, j), 0, 1);
		}
	for (int i = 2; i <= n; i++)
		for (int j = 0; j < m; j++) {
			if (j == 0 || j == m - 1) continue;
			add(id(i, j), id(i, j + 1), 0, 1);
			add(id(i, j + 1), id(i, j), 0, 1);
		}
	int R = tot;
	s = id(n + 1, m - 1) + 1;
	t = s + 1;
	for (int i = 1; i < m; i++) {
		add(s, id(1, i), inf);
	}
	for (int i = 1; i < m; i++) {
		add(id(n + 1, i), t, inf);
	}
	LL flow = 0;
	while (bfs()) flow += dfs(s, inff);
	for (int i = L; i <= R; i += 2) e[i].flow += k;
	LL res = mcmf(k);
	cout << res << "\n";
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j < m; j++) {
			cout << c[i][j] + (k - e[L].flow) + (k - e[L + 2].flow) << " ";
			L += 4; 
		}
		cout << "\n";
	}
	for (int i = 2; i <= n; i++) {
		for (int j = 0; j < m; j++) {
			if (j == 0 || j == m - 1) {
				cout << d[i][j] << " ";
				continue;
			}
			cout << d[i][j] + (k - e[L].flow) + (k - e[L + 2].flow) << " ";
			L += 4;
		}
		cout << "\n";
	}
	for (int i = 2; i <= tot; i++) e[i].clr();
	tot = 1;
	fill(hd + 1, hd + t + 1, 0);
}
bool Med;
int main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t; cin >> t;
	while (t--) solve();
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}
/*
2
3 4 6
2 1 15
7 1 9
13 3 2
3 6 1 2
5 2 15 3

3 3 3
1 1
2 2
3 3
1 1 1
2 2 2
*/

F. Equivalent Rewriting

建有向图 \(G\),其中 \(i \to j\) 当且仅当某个位置最后是 \(j\),且 \(i\) 也操作过该位置。问题即为判断拓扑序是否唯一。

直接跑拓扑排序,如果某个时刻队列中有不少于 \(2\) 个点就有解。随便找两个点构造即可。时间复杂度 \(\mathcal{O}(n)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <int, int> pi;
#define fi first
#define se second
constexpr int N = 1e6 + 5; 
bool Mbe;
int n, m, ns[N], d[N];
vector <int> buc[N], e[N];
void solve() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		int k;
		cin >> k;
		for (int j = 1; j <= k; j++) {
			int x;
			cin >> x;
			buc[x].emplace_back(i);
		}
	}
	for (int i = 1; i <= m; i++) {
		if (buc[i].size() <= 1) continue;
		int p = buc[i].back();
		for (auto x : buc[i]) {
			if (x == p) break;
			e[x].emplace_back(p);
			d[p]++;
		}
	}
	queue <int> q;
	for (int i = 1; i <= n; i++) {
		if (d[i] == 0) q.push(i);
	}
	bool ok = false;
	int x = -1, y = -1;
	auto chk = [&]() {
		if (q.size() > 1) {
			x = q.front();
			q.pop();
			y = q.front();
			q.pop();
			ok = true;
		}	
		return ok; 
	};
	while (!q.empty()) {
		if (chk() == true) break;
		int u = q.front();
		q.pop();
		for (auto v : e[u]) {
			if (--d[v] == 0) q.push(v);
		}
	}
	if (ok) {
//		cout << x << " " << y << "\n";
		iota(ns + 1, ns + n + 1, 1);
		if (x > y) swap(x, y);
		for (int i = y; i >= x + 1; i--) ns[i] = ns[i - 1];
		ns[x] = y;
		cout << "Yes\n";
		for (int i = 1; i <= n; i++) cout << ns[i] << " ";
		cout << "\n";
	} else {
		cout << "No\n";
	}
	for (int i = 1; i <= n; i++) {
		d[i] = 0;
		e[i].clear();
	}
	for (int i = 1; i <= m; i++) buc[i].clear();
}
bool Med;
int main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t; cin >> t;
	while (t--) solve();
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}
/*
3
3 6
3 3 1 5
2 5 3
2 2 6

2 3
3 1 3 2
2 3 1

1 3
2 2 1
*/

G. Knapsack

考虑如果已经确定了最终选了哪些物品,显然把其中费用最大的 \(k\) 个零元购是最优的。

因此如果我们将所有物品按照 \(w_i\) 从大到小排序,那么最优策略一定存在一个分界点 \(M\),满足 \(i ≤ M\) 的物品中,价值前 \(k\) 大的物品被零元购。对于每个 \(i\),可以通过维护一个堆来预处理出前 \(i\) 个物品被免费选走的物品的价值之和。因此我们只需要对每个后缀维护出 \(01\) 背包的结果即可。

时间复杂度 \(\mathcal{O}(nW + nk + n \log n)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <int, int> pi;
#define fi first
#define se second
constexpr int N = 5e3 + 5, M = 1e4 + 5;
bool Mbe;
int n, m, k, v[N], c[N]; pi d[N];
LL f[N][M], ans;
void solve() {
	cin >> n >> m >> k;
	for (int i = 1; i <= n; i++) {
		cin >> d[i].fi >> d[i].se;
	}
	sort(d + 1, d + n + 1);
	for (int i = 1; i <= n; i++) {
		v[i] = d[i].fi, c[i] = d[i].se; 
//		cout << v[i] << " " << c[i] << "\n";
	}
	for (int i = 1; i <= n; i++) 
		for (int j = 1; j <= m; j++) {
			f[i][j] = f[i - 1][j];
			if (j >= v[i]) f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + c[i]);
		}
	priority_queue <int> q;
	LL cur = 0;
	auto upd = [&](int x) {
		if (q.size() < k) {
			q.push(-x);
			cur += x;
		} else if (-q.top() < x) {
			cur -= -q.top();
			q.pop();
			q.push(-x);
			cur += x;
		}
	};
	if (k == 0) {
		cout << f[n][m] << "\n";
		return; 
	}
	for (int i = n; i >= 0; i--) {
		LL val = f[i][m];
//		cout << val << " " << cur << "\n";
		ans = max(ans, cur + val);
		upd(c[i]);
	}
	cout << ans << "\n";
}
bool Med;
int main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t; t = 1;
	while (t--) solve();
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}
/*
10 50 1
12 516184197
25 89514235
30 521894533
31 388523197
31 480227821
35 28242326
36 114173760
38 163268500
42 971377551
44 182173741
*/

I. Counter

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <int, int> pi;
#define fi first
#define se second
constexpr int N = 1e6 + 5; 
bool Mbe;
int n, m;
pi c[N];
void solve() {
	cin >> n >> m;
	bool ok = true;
	for (int i = 1; i <= m; i++) {
		cin >> c[i].fi >> c[i].se;
	}
	sort(c + 1, c + m + 1);
	for (int i = 1; i <= m; i++) 
		if (c[i - 1].fi != c[i].fi) {
			if (c[i].se - c[i - 1].se != c[i].fi - c[i - 1].fi) {
				ok &= c[i].fi - c[i - 1].fi >= c[i].se + (c[i - 1].se > 0);
			}
		} else {
			ok &= c[i].se == c[i - 1].se;
		}
	if (ok) cout << "Yes\n";
	else cout << "No\n"; 
}
bool Med;
int main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t; cin >> t;
	while (t--) solve();
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}
/*
3
7 4
4 0
2 2
7 1
5 1

3 2
2 2
3 1

3 1
3 100
*/

J. Suffix Structure

首先有一个暴力 \(\mathcal{O}(n^2)\) 做法:建出 ACAM,枚举 \(i\),扫 \(t\) 的所有字符,在自动机上走,经过的所有点的深度即为 \(f(i,j)\)

接下来的一步非常厉害。考虑对于一个点 \(i\),找到它第一次跳 \(fail\) 的位置,假设是在第 \(t\) 个字符跳到了点 \(u\),那么如果 \(u\) 的深度 \(\geq t\),后续部分和从 \(anc(u,t)\) 出发一样,其中 \(anc(u,t)\) 表示从 \(u\) 往上跳 \(t\) 次父亲得到的点。如果 \(u\) 的深度 \(<t\),那么根据 \(fail\) 的极大性可以证明后续部分和从根出发一样。

如果是第一种情况,\(i\)\(anc(u,t)\) 给答案 \(g_i\) 贡献的答案只有一个前缀有区别,且区别是一个定值。如果是第二种情况,就需要提前加上前 \(t\) 次的贡献,需要前缀加等差数列。然后加上从根出发的贡献,再扣掉从根出发的前 \(t\) 次的贡献。

可以维护一个 \(v_u\) 表示当前扫过的点中有多少个贡献等价于从 \(u\) 出发。然后转移就是 \(v_{anc(u,t)} \gets v_u\),且让 \(g\) 的一个前缀加上一个定值乘上 \(v_u\)。最后一路维护上去变成一堆和根相关的贡献。记一下每个深度的出现次数,算一下根的每个 \(j\) 的答案。可以通过差分前缀和 \(\mathcal{O}\) 处理。

总时间复杂度 \(\mathcal{O}((n+m) \log n)\)

code
#include <bits/stdc++.h>
using namespace std;
#define int long long
typedef long long LL;
typedef unsigned long long ull;
typedef pair <int, int> pi;
#define fi first
#define se second
constexpr int N = 4e5 + 5;
const ull base = 1e9 + 7; 
bool Mbe;
ull pw[N], ths[N], ahs[N];
int val[N], dlt[N];
int n, m, fa[N], c[N], a[N], ans[N], dep[N];
int nxt[N], nxv[N], anc[N][30];
unordered_map <ull, int> id;
struct info {
	int fail;
	map <int, int> ch;
} T[N];
vector <int> vec;
queue <int> q;
void build() {
	for (auto p : T[0].ch) {
		int i = p.fi, u = p.se;
		T[u].fail = 0, dep[u] = 1, q.push(u);
		ths[u] = i;
	}
	while (!q.empty()) {
		int u = q.front(); q.pop();
		vec.push_back(u);
		for (auto p : T[u].ch) {
			int i = p.fi, v = p.se;
			dep[v] = dep[u] + 1, q.push(v);
			ths[v] = ths[u] * base + (ull)(i);
			int w = T[u].fail;
			while (w && !T[w].ch.count(i)) w = T[w].fail;
			if (T[w].ch.count(i)) T[v].fail = T[w].ch[i]; 
		}
	}
	for (int i = 1; i <= n; i++) id[ths[i]] = i;
}
void calc(int u) {
	int l = 0, r = m, res = -1;
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (id.count(ths[u] * pw[mid] + ahs[mid])) res = mid, l = mid + 1;
		else r = mid - 1;
	}
	nxt[u] = res;
	nxv[u] = id[ths[u] * pw[res] + ahs[res]];
}
int climb(int u, int k) {
	for (int i = 0; i <= 20; i++)
		if (k >> i & 1) u = anc[u][i];
	return u; 
}
void add0(int l, int r, int v) {
	ans[l] += v, ans[l + 1] -= v, ans[r + 1] -= v, ans[r + 2] += v; 
}
void add1(int l, int r, int v, int d) {
	add0(l, r, v * d);
	ans[l] += d, ans[r + 1] -= d, ans[r + 1] -= r * d, ans[r + 2] += r * d;
}
void add_dlt(int l, int r, int v) {
	dlt[l] -= v, dlt[r + 1] += v;
}
void calc_ans() {
	val[0] = 0;
	for (int i = 1; i <= n; i++) val[i] = 1;
	for (int j = n - 1; j >= 0; j--) {
		int i = vec[j];
		int t = nxt[i], u = nxv[i];
		if (dep[T[u].fail] >= t) {
			int f = climb(T[u].fail, t);
			val[f] += val[i];
			add0(1, t, val[i] * (dep[i] - dep[f]));
		} else {
			int f = 0;
			val[f] += val[i];
			add_dlt(1, t, val[i]);
			add1(1, t, dep[i], val[i]);
		}
	}
	dlt[1] += val[0];
	for (int i = 1; i <= m; i++) dlt[i] += dlt[i - 1];
	for (int i = 1; i <= m; i++) ans[i] += ans[i - 1];
	for (int i = 1; i <= m; i++) ans[i] += ans[i - 1];
	
	int u = 0;
	for (int j = 1; j <= m; j++) {
		while (u && !T[u].ch.count(a[j])) u = T[u].fail;
		if (T[u].ch.count(a[j])) {
			u = T[u].ch[a[j]];
			ans[j] += dep[u] * dlt[j];
		} 
	}
}
void solve() {
	cin >> n >> m;
	pw[0] = 1;
	for (int i = 1; i <= n; i++) pw[i] = pw[i - 1] * base;
	for (int i = 1; i <= n; i++) cin >> fa[i], anc[i][0] = fa[i];
	for (int i = 1; i <= n; i++) {
		cin >> c[i];
		T[fa[i]].ch[c[i]] = i;
	}
	for (int i = 1; i <= m; i++) cin >> a[i];
	for (int i = 1; i <= m; i++) ahs[i] = ahs[i - 1] * base + (ull)(a[i]);
	build();
	for (int i = 1; i <= n; i++) calc(i);
	for (int j = 1; j <= 20; j++)
		for (int i = 1; i <= n; i++) anc[i][j] = anc[anc[i][j - 1]][j - 1];
	calc_ans();
	for (int i = 1; i <= m; i++) cout << ans[i] << " \n"[i == m];
	
	n = m = max(n, m) + 10;
	for (int i = 0; i <= n; i++) T[i].fail = dep[i] = 0, T[i].ch.clear();
	for (int i = 0; i <= m; i++) ans[i] = dlt[i] = 0;
	vec.clear();
	id.clear();
}
bool Med;
signed main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t; cin >> t;
	while (t--) solve();
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}
/*
2
11 3
0 1 2 0 4 5 4 6 0 9 10
1 3 2 2 1 3 4 1 3 2 1
3 2 4

5 16
0 0 0 1 4
1 2 3 2 2
2 1 3 3 2 1 3 2 1 3 2 2 1 1 2 1
*/

K. Grand Finale

注意到如果手牌上限为 \(k\) 可以,那么手牌上限大于 \(k\) 也一定可以。另外注意到,手牌数量一定是单调不减的。考虑枚举手牌抽到上限的那个瞬间。

具体来说,记 \(dp(i,j)\) 表示抽完牌堆前 \(i\) 张牌后,是否可能手牌中有 \(j\)Q,且没有爆过牌。这里因为抽到的牌的集合是已知的,且抽牌的数量也是已知的,我们能够简单计算出手牌中有多少张 Q。记 \(f(i,j)\) 表示抽完牌堆前 \(i\) 张牌后,当前手牌数量已经到达上限,此时如果手牌中当前有 \(j\)B,最少需要多少个 Q 才能抽完牌堆。这里利用了手牌上限的单调性。

转移都很简单。计算答案则为枚举 \(i,j\),检查 \(dp(i,j)\) 合法的情况下手中 Q 的数量是否支持我们抽完牌堆。如果可以,就可以计算出当前手牌个数,更新答案。时间复杂度 \(\mathcal{O}(nm)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <int, int> pi;
#define fi first
#define se second
constexpr int N = 2.5e3 + 5, inf = 1e9;
bool Mbe;
int n, m;
char a[N], b[N];
bool dp[N][2 * N];
int f[N][2 * N], cq[N], cb[N], ct[N];
void chkmn(int &x, int y) {
	x = min(x, y);
}
void solve() {
	cin >> n >> m;
	for (int i = 0; i <= m; i++)
		for (int j = 0; j <= n + i; j++) {
			dp[i][j] = false;
		}
	cin >> a + 1 >> b + 1;
	int sq = 0;
	int sb = 0;
	for (int i = 1; i <= n; i++) {
		sq += a[i] == 'Q';
		sb += a[i] == 'B';
	}
	int st = n - sq - sb;
	for (int i = 1; i <= m; i++) {
		cq[i] = cq[i - 1] + (b[i] == 'Q');
		cb[i] = cb[i - 1] + (b[i] == 'B');
		ct[i] = ct[i - 1] + (b[i] == 'W'); 
	}
	for (int i = 0; i <= sb + cb[m]; i++) {
		f[m][i] = f[m + 1][i] = 0;
	}
	for (int i = m - 1; i >= 0; i--) 
		for (int j = 0; j <= sb + cb[i]; j++) {
			f[i][j] = max(0, f[i + 1][j + (b[i + 1] == 'B')] - (b[i + 1] == 'Q')) + 1; 
			if (j) chkmn(f[i][j], max(0, f[i + 2][j - 1 + (b[i + 1] == 'B')] - (b[i + 1] == 'Q')));
		}
	dp[0][sq] = true;
	int ans = inf;
	for (int i = 0; i <= m - 1; i++)
		for (int j = 0; j <= n + i; j++) 
			if (dp[i][j]) {
				int cnb = sb + cb[i] - (i - (sq + cq[i] - j)) / 2; 
				int cnt = j + cnb + st + ct[i];
				if (f[i][cnb] <= j) {
					chkmn(ans, cnt);
				}
				if (j) {
					if (i == m - 1) chkmn(ans, cnt);
					else dp[i + 1][j - 1 + (b[i + 1] == 'Q')] = true;
				} 
				if (cnb) {
					if (i == m - 1 || i == m - 2) chkmn(ans, cnt);
					else dp[i + 2][j + (b[i + 1] == 'Q') + (b[i + 2] == 'Q')] = true;
				}
			}
	if (ans == inf) cout << "IMPOSSIBLE\n";
	else cout << ans << "\n";
}
bool Med;
int main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t; cin >> t;
	while (t--) solve();
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}
/*
2
2 6
BG
BQWBWW
4 6
GQBW
WWWWQB
*/

L. Elevator

直接贪心,把所有物品按照 \(v\) 排序,每次按照 \(v\) 从大到小选物品,如果当前只剩 \(1\) 容量但当前物品大小为 \(2\),只需额外选择一个下面 \(v\) 最大的大小为 \(1\) 的物品即可。把这些物品分为一组,重复执行该操作直到取完所有物品。

来自官方题解的证明

我们通过说明该贪心策略产生的答案达到了下界来说明最优性。为了求出答案的下界,我们将所有重量为 \(2\) 的包裹拆成两个重量为 \(1\) 的包裹,变成这样一个新问题:

给一个序列 \(a_1,a_2,\cdots,a_n\),将序列中的所有数分成若干组,每组最多包含 \(k\) 个数。一个分组方案的得分是所有组最大值的和,求最小得分。

由于原问题中的任何一个配送方案都能对应新问题中的一个配送方案,因此新问题的答案不比原问题大,也就是说新问题的答案就是原问题答案的下界。显然新问题可以贪心解决,答案就是所有下标对 \(k\) 取模等于 \(1\) 的数的和。接下来证明原问题的贪心策略也能得到这个答案。分为两种情况。在示意图中,我们用红色箭头指向每趟电梯的最大楼层,也就是真正消耗电量的包裹。

  • 情况一:前面总重量为 \(k\) 的货物恰好能装满一趟电梯。此时原问题和新问题消耗了相同的电能。

  • 情况二:出现电梯容量只剩 \(1\),但是下一个包裹的重量却是 \(2\) 的情况。

由于 \(k\) 是偶数,因此在下一个重量为 \(1\) 的包裹出现之前,新问题的红箭头指向的都是大包裹的后半部分。在原问题的贪心策略中,我们把下一个重量为 \(1\) 的包裹移到了前面,那么中间的所有大包裹都会右移一个位置,红箭头也从大包裹的后半部分移到了前半部分。但所有红箭头指向的仍然是同一个包裹,因此答案也没有改变。于是正确性得证。

实现有一些细节。时间复杂度 \(\mathcal{O}(n \log n)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <int, int> pi;
#define fi first
#define se second
constexpr int N = 1e5 + 5;
bool Mbe;
LL n, k, ans;
struct item {
	LL c, w, v;
	bool operator < (const item &p) const {
		return v > p.v;
	}
} a[N];
void solve() {
	cin >> n >> k;
	ans = 0;
	for (int i = 1; i <= n; i++) {
		cin >> a[i].c >> a[i].w >> a[i].v;
	}
	sort(a + 1, a + n + 1);
	int pos = 1;
	for (int i = 1, j; i <= n; i = j) {
		if (!a[i].c) {
			j = i + 1;
			continue;
		}
		LL kk = k;
		j = i;
		while (j <= n && kk >= a[j].w * a[j].c) kk -= a[j].w * a[j].c, a[j].c = 0, j++;
		if (j == i) {
			LL t = kk / a[i].w;
			LL cc = a[i].c / t;
			ans += a[i].v * cc;
			a[i].c -= cc * t;
 		} else {
 			ans += a[i].v;
 			if (j <= n) {
 				LL t = kk / a[j].w;
				kk -= t * a[j].w;
				a[j].c -= t;
				if (kk) {
					while (pos <= n && (pos < j || !a[pos].c || a[pos].w == 2)) pos++;
					a[pos].c--;
				}
			}
		}
	}
	cout << ans << "\n";
}
bool Med;
int main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t; cin >> t;
	while (t--) solve();
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}
/*
2
4 6
1 1 8
7 2 5
1 1 7
3 2 6
8 1200000
100000 1 100000
100000 1 12345
100000 2 100000
100000 2 12345
100000 1 100000
100000 1 12345
100000 2 100000
100000 2 12345
*/

M. Trapping Rain Water

考虑序列全局最大值的位置。左边的部分贡献是前缀 \(\max\),后面的部分是后缀 \(\min\),两个可以分别建一颗线段树维护,查询只需要求区间和。

考虑如何维护修改。以前缀 \(\max\) 为例,由于只会增加,只有当修改后成为前缀 \(\max\) 才会真正有影响。设修改后为 \(v\),找到之后第一个 \(>v\) 的位置 \(R\),那么影响其实就是区间 \([i,R-1]\) 的前缀 \(\max\) 变成 \(v\)。在前缀 \(\max\) 对应的线段树上区间覆盖即可。求 \(R\) 只需要再开一颗线段树维护区间 \(\max\),查询线段树上二分即可。

总时间复杂度 \(\mathcal{O}(n \log n)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <int, int> pi;
#define fi first
#define se second
constexpr int N = 1e6 + 5;
#define m ((l + r) >> 1)
#define lc x << 1
#define rc lc | 1
bool Mbe;
LL n, a[N], pre[N], suf[N];
struct SGT1 {
	LL mx[N << 2];
	void build(int x, int l, int r) {
		if (l == r) {
			mx[x] = a[l];
			return;
		}
		build(lc, l, m);
		build(rc, m + 1, r);
		mx[x] = max(mx[lc], mx[rc]);
	}
	void upd(int x, int l, int r, int p, LL v) {
		if (l == r) {
			mx[x] = v;
			return;
		}
		if (p <= m) upd(lc, l, m, p, v);
		else upd(rc, m + 1, r, p, v);
		mx[x] = max(mx[lc], mx[rc]);
	}
	int qryL(int x, int l, int r, int ql, int qr, LL v) {
		if (mx[x] <= v) return 0;
		if (l == r) return l;
		if (qr > m) {
			int res = qryL(rc, m + 1, r, ql, qr, v);
			if (res) return res;
		}
		if (ql <= m) return qryL(lc, l, m, ql, qr, v);
		return 0;
	}
	int qryR(int x, int l, int r, int ql, int qr, LL v) {
		if (mx[x] <= v) return 0;
		if (l == r) return l;
		if (ql <= m) {
			int res = qryR(lc, l, m, ql, qr, v);
			if (res) return res;
		}
		if (qr > m) return qryR(rc, m + 1, r, ql, qr, v);
		return 0;
	}
} sgt;
struct SGT2 {
	LL sum[N << 2], tag[N << 2], len[N << 2];
	void push_up(int x) {
		sum[x] = sum[lc] + sum[rc];
	}
	void push_tag(int x, LL v) {
		tag[x] = v;
		sum[x] = len[x] * v;
	}
	void push_down(int x) {
		if (tag[x]) {
			push_tag(lc, tag[x]);
			push_tag(rc, tag[x]);
			tag[x] = 0;
		}
	}
	void build(int x, int l, int r, LL* val) {
		sum[x] = tag[x] = 0;
		len[x] = r - l + 1;
		if (l == r) {
			sum[x] = val[l];
			return;
		}
		build(lc, l, m, val);
		build(rc, m + 1, r, val);
		push_up(x);
	}
	void cov(int x, int l, int r, int ql, int qr, LL v) {
		if (ql <= l && qr >= r) {
			push_tag(x, v);
			return;
		}
		push_down(x);
		if (ql <= m) cov(lc, l, m, ql, qr, v);
		if (qr > m) cov(rc, m + 1, r, ql, qr, v);
		push_up(x);
	}
	LL qry(int x, int l, int r, int ql, int qr) {
		if (ql <= l && qr >= r) {
			return sum[x];
		}
		push_down(x);
		LL res = 0;
		if (ql <= m) res += qry(lc, l, m, ql, qr);
		if (qr > m) res += qry(rc, m + 1, r, ql, qr);
		return res;
	}
} tp, ts;
#undef m
void solve() {
	cin >> n;
	int pos = 0;
	LL sum = 0;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		if (a[i] > a[pos]) pos = i;
		sum += a[i];
	}
	pre[0] = suf[n + 1] = 0;
	for (int i = 1; i <= n; i++) {
		pre[i] = max(pre[i - 1], a[i]);
	}
	for (int i = n; i >= 1; i--) {
		suf[i] = max(suf[i + 1], a[i]);
	}
	sgt.build(1, 1, n);
	tp.build(1, 1, n, pre);
	ts.build(1, 1, n, suf);
	int m;
	cin >> m;
	while (m--) {
		int x, y;
		cin >> x >> y;
		a[x] += y;
		if (a[x] > a[pos]) pos = x;
		sum += y;
		sgt.upd(1, 1, n, x, a[x]);
		int nl = x == 1 ? 0 : sgt.qryL(1, 1, n, 1, x - 1, a[x]);
		int nr = x == n ? 0 : sgt.qryR(1, 1, n, x + 1, n, a[x]);
		if (!nr) nr = n + 1;
		if (!nl) {
			tp.cov(1, 1, n, x, nr - 1, a[x]);
		} 
		if (nr == n + 1) {
			ts.cov(1, 1, n, nl + 1, x, a[x]);
		}
		LL cur = 0;
		cur += ts.qry(1, 1, n, pos, n);
//		cout << cur << " !1\n";
		if (pos > 1) cur += tp.qry(1, 1, n, 1, pos - 1);
//		cout << cur << " !2\n";
		cout << cur - sum << "\n";
	}
}
bool Med;
int main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t; cin >> t;
	while (t--) solve();
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}
/*
2
6
1 2 3 4 5 6
2
1 2
3 3

5
100 10 1 10 100
1
3 100
*/
posted @ 2023-12-22 10:57  came11ia  阅读(27)  评论(0编辑  收藏  举报