牛客周赛 Round 103


A. 清楚

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

using i64 = long long;

void solve() {
	int n;
	std::cin >> n;
	if (n % 10) {
		std::cout << "YES\n";
	} else {
		std::cout << "NO\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;
}

B. 姐姐

题意:给定一个环形数组,你可以选定一个起点,这样就破环成链了,求有没有办法使得这个数组不递减。

把数组复制一份到后面,然后从前往后记录有多少个数是不递减的,当个数到\(n\)时说明以\(i-n\)作为起点可以使得数组不递减。

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

using i64 = long long;

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

	a.insert(a.end(), a.begin(), a.end());
	for (int i = 1, cnt = 1; i < 2 * n; ++ i) {
		if (a[i] >= a[i - 1]) {
			++ cnt;
		} else {
			cnt = 1;
		}

		if (cnt == n) {
			std::cout << "YES\n";
			return;
		}
	}
	std::cout << "NO\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;
}

C. 智乃

题意:一个长度为\(n\)的排列为好排列当且仅当该排列满足恰好存在\(n\)个子区间是一个排列。求长度为\(n\)的排列有多少个好排列。

因为要有\(n\)个子区间是排列,那么\(n\)要么在第一个要么在最后一个。则\(cnt_n = cnt_{n-1} \times 2\)。因为长度为\(1\)的好排列只有一个,可得\(cnt_n = 2^{n-1}\)

代码省略取模类。

点击查看代码
constexpr int P = 1000000007;
using Z = MInt<P>;

void solve() {
	int n;
	std::cin >> n;
	std::cout << power<Z>(2, n - 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;
}

D. 哥哥

题意:给你一个\(01\)串,你可以任意修改其中的字符。求\(01\)子串的个数加\(10\)子串的个数至少是\(3\)个最小修改数。

和上周牛客周赛一个题差不多,区间只在这个是至少\(3\)个,另一个是恰好\(3\)个。
那么一样的\(dp\),设\(f[i][j][k]\)为前\(i\)个有\(k\)个子串且第\(i\)个位置是\(j\)的最小修改数。我们转移时发现如果子串个数大于等于\(3\),就和\(3\)\(min\)就行了。这样\(f[i][j][3]\)就表示大于等于\(3\)的答案。

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

using i64 = long long;

void solve() {
	int n;
	std::cin >> n;
	std::string s;
	std::cin >> s;
	const int inf = 1e9;
	std::array<std::array<int, 2>, 4> f;
	for (int i = 0; i < 4; ++ i) {
		f[i].fill(inf);
	}

	for (int i = 0; i < 2; ++ i) {
		f[0][i] = s[0] - '0' != i;
	}

	for (int i = 1; i < n; ++ i) {
		std::array<std::array<int, 2>, 4> g{};
		for (int j = 0; j < 4; ++ j) {
			g[j].fill(inf);
		}
		for (int j = 0; j < 4; ++ j) {
			for (int x = 0; x < 2; ++ x) {
				for (int y = 0; y < 2; ++ y) {
					int t = x != y;
					int nj = std::min(3, j + t);

					g[nj][y] = std::min(g[nj][y], f[j][x] + (y != s[i] - '0'));
				}
			}
		}

		f = g;
	}

	int ans = std::min(f[3][0], f[3][1]);
	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;
}


E. 新婚

题意:一棵树,每个点有点权,点权为\(0\)\(1\)。一条路径的值为起点到终点构成的\(01\)串的二进制值。多次询问,有没有至少一条路径的值是\(x\),满足这个路径的一个端点是另一个端点的祖先。

\(x \leq 2^{20}\)。意味着路径长度小于等于\(20\)。那么每个点暴力往上爬就行,长度大于等于\(20\)就退出,然后每一步得到的二进制串正着一遍反着一遍算出值存到\(set\)里。

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

using i64 = long long;

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

	std::vector<int> fa(n, -1);
	std::set<int> st;

	auto get = [&](std::string & t) -> int {
		int res = 0;
		for (auto & c : t) {
			res = res * 2 + c - '0';
		}

		return res;
	};

	auto getr = [&](std::string t) -> int {
		std::ranges::reverse(t);
		int res = 0;
		for (auto & c : t) {
			res = res * 2 + c - '0';
		}

		return res;
	};

	auto dfs = [&](auto & self, int u) -> void {
		int x = 0, y = u;
		int cnt = 0;
		std::string t;
		while (cnt <= 20 && y != -1) {
			t += s[y];
			st.insert(get(t));
			st.insert(getr(t));
			y = fa[y];
            ++ cnt;
		}
		for (auto & v : adj[u]) {
			if (v == fa[u]) {
				continue;
			}

			fa[v] = u;
			self(self, v);
		}
	};

	dfs(dfs, 0);
	while (q -- ) {
		int x;
		std::cin >> x;
		if (st.count(x)) {
			std::cout << "YES\n";
		} else {
			std::cout << "NO\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. 快乐

题意:给定一个字符串\(s\)\(m\)个字符串,你可以选择\(s\)的一个后缀,使得这个后缀和所有\(m\)个字符串的最长公共前缀的长度和最大。

我们把\(m\)个字符串插到\(trie\)里,并且记录一个\(sum_u\)表示根到\(u\)这个节点被所有字符串经过的长度和。那么给定一个后缀,答案就是这个后缀在\(trie\)里代表的节点\(u\)\(sum_u\)。但如果每个后缀都查一遍依然是\(O(n^2)\)的,可以用\(AC\)自动机建出来\(fail\)树,那么给定一个字符串\(s\),一直跳\(fail\)链,那么如果一个后缀在\(m\)字符串里其中一个的前缀里出现过,就可以找到对应的节点。但可能最佳的后缀,只是有一部分前缀出现在\(m\)个字符串里,那么这需要删除\(s\)的一些后缀再进行查询才能查的到。
发现我们大可不必跳\(fail\)链,直接在构造\(fail\)树时记录\(fail\)链上最大的\(sum_u\)。可以做到\(O(1)\)查询,那么时间复杂度就只在枚举删除后缀上,那么可以存\(trie\)里每个节点父亲的编号,就可以一步一步往上爬,时间复杂度\(O(n)\)\(AC\)自动机的时间复杂度\(O(nlogn)\)

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

using i64 = long long;

constexpr int SIZE = 26;
constexpr int N = 6e5 + 5;

int tr[N][SIZE], fa[N], len[N], next[N];
i64 cnt[N], sum[N], max[N];
int idx;
struct ACAM {
	ACAM() {
		init();
	}

	int newNode() {
		memset(tr[idx], 0, sizeof tr[idx]);
		cnt[idx] = 0;
		next[idx] = 0;
		len[idx] = 0;
		sum[idx] = 0;
		fa[idx] = 0;
		return idx ++ ;
	}

	void init() {
		idx = 0;
		newNode();
	}

	int insert(const std::string & s, int add = 1) {
		int p = 0;
		for (auto & c : s) {
			int x = c - 'a';
			if (!tr[p][x]) {
				tr[p][x] = newNode();
				len[tr[p][x]] = len[p] + 1;
				fa[tr[p][x]] = p;
			}

			p = tr[p][x];
			cnt[p] += add;
		}

		return p;
	}

	void build() {
		std::queue<int> q;
		for (int i = 0; i < SIZE; ++ i) {
			if (tr[0][i]) {
				q.push(tr[0][i]);
				sum[tr[0][i]] = cnt[tr[0][i]];
			}
		}

		while (q.size()) {
			int u = q.front(); q.pop();
			for (int i = 0; i < SIZE; ++ i) {
				int v = tr[u][i];
				if (!v) {
					tr[u][i] = tr[next[u]][i];
				} else {
					next[v] = tr[next[u]][i];
					sum[v] = cnt[v] + sum[u];
					q.push(v);
				}
			}
			max[u] = std::max(sum[u], max[next[u]]);
		}
	}
};

void solve() {
	int n, m;
	std::cin >> n >> m;
	std::string s;
	std::cin >> s;
	ACAM ac;
	for (int i = 0; i < m; ++ i) {
		std::string t;
		std::cin >> t;
		ac.insert(t);
	}

	int p = ac.insert(s, 0);
	ac.build();
	i64 ans = 0;
	while (p) {
		ans = std::max(ans, max[p]);
		p = fa[p];
	}
	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-03 21:00  maburb  阅读(33)  评论(0)    收藏  举报