VP Educational Codeforces Round 32


A. Local Extrema

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    int ans = 0;
    for (int i = 1; i + 1 < n; ++ i) {
    	if ((a[i] > a[i - 1] && a[i] > a[i + 1]) || (a[i] < a[i - 1] && a[i] < a[i + 1])) {
    		++ ans;
    	}
    }

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

B. Buggy Robot

题意:给你一个操作序列,要求保留最多的操作,使得最后回到原点。

显然上下操作相等,左右操作相等。两个分别取最小值加起来。

点击查看代码
void solve() {
	int n;
	std::cin >> n;
    std::string s;
    std::cin >> s;
    std::map<char, int> mp;
    for (auto & c : s) {
    	mp[c] += 1;
    }

    int ans = std::min(mp['L'], mp['R']) * 2 + std::min(mp['U'], mp['D']) * 2;
    std::cout << ans << "\n";
}

C. K-Dominant Character

题意:求一个最小的\(k\),使得所有长度为\(k\)的子串都包含至少一个相同的字符。

二分。

点击查看代码
void solve() {
    std::string s;
    std::cin >> s;
    int n = s.size();

    std::vector<std::array<int, 26>> pre(n + 1);
    std::fill(pre[0].begin(), pre[0].end(), 0);
    for (int i = 0; i < n; ++ i) {
    	pre[i + 1] = pre[i];
    	pre[i + 1][s[i] - 'a'] += 1;
    }

    auto check = [&](int m) -> bool {
    	std::array<bool, 26> st{};
    	std::fill(st.begin(), st.end(), true);
    	for (int i = 1; i + m - 1 <= n; ++ i) {
    		for (int j = 0; j < 26; ++ j) {
    			st[j] &= pre[i + m - 1][j] - pre[i - 1][j] > 0;
    		}
    	}

    	return std::count(st.begin(), st.end(), true) > 0;
    };

    int l = 1, r = n;
    while (l < r) {
    	int mid = l + r >> 1;
    	if (check(mid)) {
    		r = mid;
    	} else {
    		l = mid + 1;
    	}
    }

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

D. Almost Identity Permutations

题意:给一个\(n\)\(k\)。求所有\(n\)的排列里有小于等于\(k\)\(p_i \neq i\)的排列数量。

\(k\)很小,可以分类讨论。
\(k=0\),有一个排列。
\(k=1\),没有满足的排列。
\(k=2\),相当于找两个数交换,方案是\(C(n, 2)\)
\(k=3\),相当于找三个数,然后它们都不能在自己位置上的数,那么可以发现只有\(2, 1, 3\)\(3, 1, 2\)这两类满足。所以是\(C(n, 3) \times 2\)
\(k=4\),和\(k=3\)一个思路,通过打表或者手算发现,选出的\(4\)个数有\(9\)种合法的排列。方案数位\(C(n, 4) \times 9\)

点击查看代码
void solve() {
    i64 n, k;
    std::cin >> n >> k;
    i64 ans = 0;
    if (k >= 1) {
    	ans += 1;
    }

    if (k >= 2) {
    	ans += n * (n - 1) / 2;
    }

    if (k >= 3) {
    	ans += n * (n - 1) * (n - 2) / 6 * 2;;
    }

    if (k >= 4) {
    	//2 1 4 3
    	//2 3 4 1
    	//2 4 1 3
    	//3 1 4 2
    	//3 4 2 1
    	//3 4 1 2
    	//4 1 2 3
    	//4 3 2 1
    	//4 3 1 2
    	ans += n * (n - 1) * (n - 2) * (n - 3) / 24 * 9;
    }

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

E. Maximum Subsequence

题意:从\(n\)个数里选若干个个数,使得它们的和对\(m\)取模最大。

\(n\)只有\(35\),貌似是搜索和枚举。但直接枚举复杂度是\(2^n\)次方,无法通过。但我们可以分成两部分枚举,找出前\(\frac{n}{2}\)数可以凑出的所有值,以及后面\(\frac{n}{2}\)数可以凑出的所有值,这样复杂度是\(2 \times 2^{\frac{n}{2}}\)
然后想办法把这两部分凑起来,可以枚举一部分,进行分类讨论,如果\(x_i + y_ i < m\),那么我们可以对于前部分的每个值二分找后部分满足 条件的最大值。如果\(x_i + y_i > m\),那么因为\(x_i, y_i\)都小于\(m\),所以我们应该取最大的\(y_i\)

点击查看代码
void solve() {
    int n, m;
    std::cin >> n >> m;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    	a[i] %= m;
    }

    std::vector<int> b, c;
    for (int i = 0; i < n; ++ i) {
    	if (i < n / 2) {
    		b.push_back(a[i]);
    	} else {
    		c.push_back(a[i]);
    	}
    }

    std::set<int> fb;
    fb.insert(0);
    for (auto & x : b) {
    	auto g = fb;
    	for (auto & y : fb) {
    		g.insert((x + y) % m);
    	}

    	fb = g;
    }

    std::set<int> fc;
    fc.insert(0);
    for (auto & x : c) {
    	auto g = fc;
    	for (auto & y : fc) {
    		g.insert((x + y) % m);
    	}

    	fc = g;
    }

    int ans = std::max(*fb.rbegin(), *fc.rbegin());
    for (auto & x : fb) {
    	ans = std::max(ans, (x + *fc.rbegin()) % m);
    	auto it = fc.lower_bound(m - x);
    	if (it != fc.begin()) {
    		-- it;
    		ans = std::max(ans, x + *it);
    	}
    }

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

F. Connecting Vertices

题意:给出一个正多边形,你要用\(n-1\)条线连接它们使得形成一棵树,每条线连两个点,且任意两条线不能相交。求方案数。

考虑区间\(dp\)\(f[i][j][0/1]\)代表\(i\)\(j\)连成一棵树且\(i, j\)有没有连边的方案数。那么如果\(i, j\)有边,就可以连接它们,枚举一个中间点\(k\),使得本来区间是两棵树\([i, k], [k + 1, j]\)\(i, j\)这条边连接它们,转移方程为\(f[i][j][0] = (f[i][k][0] + f[i][k][1]) \times (f[k + 1][j][0] + f[k + 1][j][1])\)
然后考虑\(i, j\)不连边,那么我们要找一个中间点\(k\)使得\([i, k - 1], [k, j]\)加上\(i, k\)这条边后合并,那么有\(f[i][j][1] = f[i][k][0] \times (f[k][j][0] + f[k][j][1])\)
代码省略取模类。

点击查看代码
void solve() {
	int n;
	std::cin >> n;
	std::vector g(n, std::vector<int>(n));
	for (int i = 0; i < n; ++ i) {
		for (int j = 0; j < n; ++ j) {
			std::cin >> g[i][j];
		}
	}

	std::vector f(n, std::vector(n, std::array<Z, 2>{0, 0}));
	for (int len = 1; len <= n; ++ len) {
		for (int i = 0; i + len - 1 < n; ++ i) {
			int j = i + len - 1;
			if (len == 1) {
				f[i][j][0] = 1;
			} else if (len == 2) {
				f[i][j][0] = g[i][j];
			} else {
				if (g[i][j]) {
					for (int k = i; k < j; ++ k) {
						f[i][j][0] += (f[i][k][0] + f[i][k][1]) * (f[k + 1][j][0] + f[k + 1][j][1]);
					}
				}

				for (int k = i; k < j; ++ k) {
					if (g[i][k]) {
						f[i][j][1] += f[i][k][0] * (f[k][j][0] + f[k][j][1]);
					}
				}
			}
		}
	}

	std::cout << f[0][n - 1][0] + f[0][n - 1][1] << "\n";
}

G. Xor-MST

题意:给你一个数组,它们两两连边,边权为两个数的异或和。求最小生成树。

考虑分治,从高位到低位,我们按\(bit\)分组,然后递归处理这两部分在计算它们之间的贡献,因为这两部分处理后,每个部分内部已经是一棵生成树了,那么只需要在一部分里选一个往另一部连边就行。我们需要的是异或最小,那么可以把一部分存到\(01Trie\)里,另一部分直接查询。

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

using i64 = long long;

const int N = 2e5 + 5;

int Tr[N * 30][2];
struct Trie {
	int idx;
	Trie() {
		idx = 0;
		Tr[0][0] = Tr[0][1] = 0;
	}

	int new_node() {
		++ idx;
		Tr[idx][0] = Tr[idx][1] = 0;
		return idx;
	}

	void insert(int x) {
		int p = 0;
		for (int i = 29; i >= 0; -- i) {
			int s = x >> i & 1;
			if (!Tr[p][s]) {	
				Tr[p][s] = new_node();
			}

			p = Tr[p][s];
		}
	}

	int query(int x) {
		int res = 0;
		int p = 0;
		for (int i = 29; i >= 0; -- i) {
			int s = x >> i & 1;
			if (Tr[p][s]) {
				p = Tr[p][s];
			} else {
				res += 1 << i;
				p = Tr[p][!s];
			}
		}

		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];
	}

	auto work = [&](auto & self, std::vector<int> & A, int k) -> i64 {
		if (A.empty() || k < 0) {
			return 0;
		}

		std::vector<int> a, b;
		for (auto & x : A) {
			if (x >> k & 1) {
				a.push_back(x);
			} else {
				b.push_back(x);
			}
		}

		i64 ans = self(self, a, k - 1) + self(self, b, k - 1);
		if (a.empty() || b.empty()) {
			return ans;
		}

		int min = 1 << 30;
		if (a.size() < b.size()) {
			Trie tr;
			for (auto & x : a) {
				tr.insert(x);
			}

			for (auto & x : b) {
				min = std::min(min, tr.query(x));
			}
		} else {
			Trie tr;
			for (auto & x : b) {
				tr.insert(x);
			}

			for (auto & x : a) {
				min = std::min(min, tr.query(x));
			}
		}

		return ans + min;
	};

	std::cout << work(work, a, 29) << "\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-03-13 17:00  maburb  阅读(17)  评论(0)    收藏  举报