2025牛客暑期多校训练营10


D. Grammar Test (grammar)

找规律题。取模是吓人的。发现只有\(01010..\)或者\(101010..\)两种,然后发现长度为\(4\)加上偶数个\(3\)

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	i64 n;
	std::cin >> n;
	if (n >= 4 && (n - 4) % 3 == 0 && (n - 4) / 3 % 2 == 0) {
		std::cout << 2 << "\n";
	} else {
		std::cout << 0 << "\n";
	}
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

E. Sensei and Affection (affection)

题意:一个序列,每次你可以选择一个区间使得它们加一。求最多出现\(m\)种不同数的最小操作数。\(n\leq 400, m \in {1, 2}, a_i \leq 100\)

\(m\)很小,显然可以分类讨论。
如果\(m=1\),也就是所有数都要变成一样的,肯定是变成最大值,那么每个数要加\(x - a_i\)次,这是个经典的差分问题,考虑记\(a_0 = a_{n+1} = 0\);\(d_i = a_i - a_{i-1}, i \in [1, n + 1]\)。那么一次区间操作就是在\(d\)里选一个正数一个负数然后正数减一负数加一。最终\(d\)种所有数都变成\(0\)就说明所有数都相等了。其操作数为\(\sum_{i=1}^{n+1} \max(d_i, 0) = \sum_{i=1}^{n+1} \max(a_i - a_{i-1}, 0)\)。发现\(d\)中负数和正数总是能抵消的,也可以写为\(\frac{\sum_{i=1}^{n+1} |a_i - a_{i-1}|}{2}\)
如果\(m=2\),考虑算\(\sum_{i=1}^{n+1} |a_i - a_{i-1}|\),最后结果除\(2\)就行。这个情况可以变成两个数,那么可以枚举这两个数\(x, y\),记\(f[i][0/1]\)表示\(i\)变成\(x\)或者\(y\)的前缀\(|a_i - a_{i-1}|\)的最小和。那么就容易转移了,只需要枚举\(a_i\)变成\(x\)还是变成\(y\)\(i-1\)\(x\)还是\(y\)

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n, m;
	std::cin >> n >> m;
	std::vector<int> a(n + 2);
	for (int i = 1; i <= n; ++ i) {
		std::cin >> a[i];
	}

	if (m == 1) {
		int max = std::ranges::max(a);
		for (int i = 1; i <= n; ++ i) {
			a[i] = max - a[i];
		}

		int ans = 0;
		for (int i = 1; i <= n + 1; ++ i) {
			ans += std::max(0, a[i] - a[i - 1]);
		}
		std::cout << ans << "\n";
	} else {
		int max = std::ranges::max(a);
		int min = std::ranges::min(a);

		const int inf = 1e9;
		int ans = inf;

		for (int x = min; x <= 200; ++ x) {
			for (int y = std::max(max, x); y <= 200; ++ y) {
				std::array<int, 2> f{inf, inf};
				if (a[1] <= x) {
					f[0] = x - a[1];
				}

				if (a[1] <= y) {
					f[1] = y - a[1];
				}
				
				for (int i = 2; i <= n; ++ i) {
					std::array<int, 2> g{inf, inf};
					if (a[i] <= x) {
						g[0] = std::min(f[1] + std::abs(y - a[i - 1] - (x - a[i])) , f[0] + std::abs(x - a[i - 1] - (x - a[i])));
					}

					if (a[i] <= y) {
						g[1] = std::min({f[1] + std::abs(y - a[i - 1] - (y - a[i])), f[0] + std::abs(x - a[i - 1] - (y - a[i]))});
					}

					f = g;
				}

				ans = std::min({ans, f[0] + std::abs(x - a[n]), f[1] + std::abs(y - a[n])});
			}
		}
		std::cout << ans / 2 << "\n";
	}
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

F. Sensei and Yuuka Going Shopping (yuuka)

题意:一个数组要求分成三个连续的部分,使得三个部分拥有的相同的数的种类最多。

考虑枚举第一部分的右端点,然后找最优的第二部分的右端点。
\(last_x\)\(x\)\(a\)中最后出现的位置,\(next_i\)\(i\)下一个和\(a_i\)相等的位置。
那么如果枚举到\(i\)为第一部分的右端点,则这个数想要产生贡献,第二部分右端点要在\([next_i, last_{a_i} - 1]\)这个区间。如果我们转化为区间加,那么就变成了求区间最大值。
可以用线段树维护,注意一些细节即可。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

template <class Info, class Tag>
struct LazySegmentTree {
	struct Node {
		int l, r;
		Info info;
		Tag tag;
	};

	std::vector<Node> tr;
	LazySegmentTree() {}
	LazySegmentTree(int n) {
		init(n);
	}

	void init(int n) {
		tr.assign(n << 2, {});
		build(0, n - 1);
	}

	void pushdown(Node & u, const Tag & tag) {
		u.info = u.info + tag;
		u.tag = u.tag + tag;
	}

	void pushdown(int u) {
		if (tr[u].tag.exist()) {
			pushdown(tr[u << 1], tr[u].tag);
			pushdown(tr[u << 1 | 1], tr[u].tag);
			tr[u].tag.clear();
		}
	}

	void pushup(int u) {
		tr[u].info = tr[u << 1].info + tr[u << 1 | 1].info;
	}

	void build(int l, int r, int u = 1) {
		tr[u] = {l, r, {}};
		if (l == r) {
			tr[u].info.p = l;
			return;
		}

		int mid = l + r >> 1;
		build(l, mid, u << 1); build(mid + 1, r, u << 1 | 1);
		pushup(u);
	}

	void modify(int l, int r, const Tag & tag, int u = 1) {
		if (l <= tr[u].l && tr[u].r <= r) {
			pushdown(tr[u], tag);
			return;
		}

		pushdown(u);
		int mid = tr[u].l + tr[u].r >> 1;
		if (l <= mid) {
			modify(l, r, tag, u << 1);
		}

		if (r > mid) {
			modify(l, r, tag, u << 1 | 1);
		}

		pushup(u);
	}

	Info query(int l, int r, int u = 1) {
		if (l <= tr[u].l && tr[u].r <= r) {
			return tr[u].info;
		}

		pushdown(u);
		int mid = tr[u].l + tr[u].r >> 1;
		if (r <= mid) {
			return query(l, r, u << 1);
		} else if (l > mid) {
			return query(l, r, u << 1 | 1);
		}

		return query(l, r, u << 1) + query(l, r, u << 1 | 1);
	}
};

struct Info {
	int max, p;
};

struct Tag {
	int add;
	bool exist() {
		return add != 0;
	}

	void clear() {
		add = 0;
	}
};

Info operator + (const Info & a, const Info & b) {
	return a.max >= b.max ? a : b;
}

Info operator + (const Info & a, const Tag & b) {
	Info res{};
	res.max = a.max + b.add;
	res.p = a.p;
	return res;
}

Tag operator + (const Tag & a, const Tag & b) {
	Tag res{};
	res.add = a.add + b.add;
	return res;
}

void solve() {
	int n;
	std::cin >> n;
	std::vector<int> a(n);
	for (int i = 0; i < n; ++ i) {
		std::cin >> a[i];
	}

	std::vector<int> next(n, -1);
	std::map<int, int> mp, last;
	for (int i = n - 1; i >= 0; -- i) {
		if (mp.count(a[i])) {
			next[i] = mp[a[i]];
		} else {
			last[a[i]] = i;
		}
		mp[a[i]] = i;
	}

	int ans = 0, ansl = 1, ansr = n - 1;
	LazySegmentTree<Info, Tag> tr(n);
	std::map<int, int> pre;
	for (int i = 0; i + 1 < n; ++ i) {
		int x = a[i];
		if (next[i] == -1) {

		} else if (last[x] == next[i]) {
			if (pre.count(x)) {
				tr.modify(i, last[x] - 1, Tag{-1});
			}
		} else {
			if (!pre.count(x)) {
				tr.modify(next[i], last[x] - 1, Tag{1});
			} else {
				tr.modify(i, last[x] - 1, Tag{-1});
				tr.modify(next[i], last[x] - 1, Tag{1});
			}
		}
		pre[a[i]] = i;
		auto [max, p] = tr.query(i + 1, n - 1);
		if (max > ans) {
			ans = max;
			ansl = i + 1;
			ansr = p + 1;
		}
	}
	std::cout << ans << "\n";
	std::cout << ansl + 1 << " " << ansr + 1 << "\n";
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

H. Rev Equation (NOI-tAUqe ver.) (equation)

可以看出两个运算的数相同那么最短的回文肯定是前面添等于,所以无解。
除了这个情况,还有除了\(0-x=\)这种减法有解外,其它都无解。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	std::string s;
	std::cin >> s;
	if (s[0] == s[2]) {
		std::cout << "No\n";
		return;
	}

	if (s[1] == '-' && s[0] != '0') {
		std::cout << "No\n";
		return;
	}

	std::cout << "Yes\n";
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

I. Matrix (matrix)

题意:构造题。有一个\(n\times m\)的循环矩阵。你要把\([1, n\times m]\)的数一次填上去,使得\(i\)从上下左右某个方向走\(i\)步可以到\(i+1\)

玩了半天才看出来,只能说构造题都有点找规律的感觉。
\(1\)填在\((1, 1)\),然后按左右左右的方向交错着填,每填满一行后需要换行,就上下上下交错着来。然后\(lcm(n, m) < nm\)无解。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

const int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};

void solve() {
	int n, m;
	std::cin >> n >> m;
	if (std::lcm(n, m) < n * m) {
		std::cout << "NO\n";
		return;
	}

	std::vector a(n, std::vector<int>(m));
	for (int k = 1, x = 0, y = 0, dx = 0, dy = 0; k <= n * m; ++ k) {
		a[x][y] = k;
		if (k % m == 0) {
			if (dx == 0) {
				x = ((x - k) % n + n) % n;
			} else {
				x = (x + k) % n;
			}
			dx ^= 1;
		} else {
			if (dy == 0) {
				y = ((y - k) % m + m) % m;
			} else {
				y = (y + k) % m;
			}
			dy ^= 1;
		}
	}

	std::cout << "YES\n";
	for (int i = 0; i < n; ++ i) {
		for (int j = 0; j < m; ++ j) {
			std::cout << a[i][j] << " \n"[j == m - 1];
		}
	}
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	// std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

K. Amazing Sets (amazing)

题意:一棵有向树,根可以到所有点。每个点有权值,现在加了一些边\((u, v)\),那么\(v\)\(u\)的祖先。然后选出一些点集来,使得每个点可以到底的点都在这个点集内,求所有合法点集有多少不同的和。

如果不加边,问题就是选若干个子树,那么可以树上背包。
现在加了边,可以考虑缩点,发现缩点后依然是一棵有向数,且根可以到任意点。那么树上背包就行。

点击查看代码

#include <bits/stdc++.h>

using i64 = long long;

bool f[10001][10001];

void solve() {
	int n;
	std::cin >> n;
	std::vector<int> a(n);
	for (int i = 0; i < n; ++ i) {
		std::cin >> a[i];
	}

	std::vector<std::vector<int>> adj(n);
	std::vector<int> in(n);
	for (int i = 1; i < n; ++ i) {
		int u, v;
		std::cin >> u >> v;
		-- u, -- v;
		adj[u].push_back(v);
		++ in[v];
	}

	int root = 0;
	while (in[root]) {
		++ root;
	}

	int m;
	std::cin >> m;
	for (int i = 0; i < m; ++ i) {
		int u, v;
		std::cin >> u >> v;
		-- u, -- v;
		adj[u].push_back(v);
	}

	std::vector<int> dfn(n, -1), low(n), id(n, -1), stk(n + 10), in_stk(n), w(n);
	int top = 0, idx = 0, cnt = 0;
	auto tarjan = [&](auto & self, int u) -> void {
		dfn[u] = low[u] = idx ++ ;
		stk[ ++ top] = u; in_stk[u] = 1;
		for (auto & v : adj[u]) {
			if (dfn[v] == -1) {
				self(self, v);
				low[u] = std::min(low[u], low[v]);
			} else if (in_stk[v]) {
				low[u] = std::min(low[u], dfn[v]);
			}
		}

		if (low[u] == dfn[u]) {
			int v;
			do {
				v = stk[top -- ];
				in_stk[v] = 0;
				id[v] = cnt;
				w[cnt] += a[v];
			} while (v != u);
			++ cnt;
		}
	};

	tarjan(tarjan, root);

	std::vector<std::vector<int>> adj1(cnt);
	for (int u = 0; u < n; ++ u) {
		for (auto & v : adj[u]) {
			if (id[u] != id[v]) {
				adj1[id[u]].push_back(id[v]);
			}
		}
	}

	std::vector<int> sum(cnt);
	auto dfs = [&](auto & self, int u) -> void {
		f[u][0] = true;
		std::ranges::sort(adj1[u]);
		adj1[u].erase(std::unique(adj1[u].begin(), adj1[u].end()), adj1[u].end());
		for (auto & v : adj1[u]) {
			self(self, v);
			for (int i = sum[u]; i >= 0; -- i) {
				if (f[u][i]) {
					for (int j = sum[v]; j > 0; -- j) {
						f[u][i + j] |= f[v][j];
					}
				}
			}
			sum[u] += sum[v];
		}
		sum[u] += w[u];
		f[u][sum[u]] = true;
	};

	root = id[root];
	dfs(dfs, root);
	int ans = 0;
	for (int i = 0; i <= sum[root]; ++ i) {
		ans += f[root][i];
	}
	std::cout << ans << "\n";
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	// std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}
posted @ 2025-08-15 17:40  maburb  阅读(141)  评论(0)    收藏  举报