VP Educational Codeforces Round 25
A. Binary Protocol
题意:一个数字的数位上的数变成了其值为长度的连续个1,并用0把这些串连接在一起。现在要你恢复原来的数字。
用个\(sum\)记录连续的1的个数,每次遇到0就把\(sum\)加入答案,并使\(sum=0\)。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
std::string ans;
int cnt = 0;
for (int i = 0; i < n; ++ i) {
if (s[i] == '1') {
++ cnt;
} else {
ans += std::to_string(cnt);
cnt = 0;
}
}
ans += std::to_string(cnt);
std::cout << ans << "\n";
}
B. Five-In-a-Row
题意:两个人下五子棋,判断下一回合能不能赢。
模拟题。每个空位往八个方向枚举就行了。
点击查看代码
void solve() {
int n = 10;
std::vector<std::string> s(n);
for (int i = 0; i < n; ++ i) {
std::cin >> s[i];
}
auto get = [&](int x, int y, int dx, int dy) -> int {
int res = 0;
x += dx, y += dy;
while (x >= 0 && x < n && y >= 0 && y < n && s[x][y] != 'O' && s[x][y] != '.') {
++ res;
x += dx, y += dy;
}
return res;
};
for (int i = 0; i < n; ++ i) {
for (int j = 0; j < n; ++ j) {
if (s[i][j] == '.') {
if (get(i, j, 0, -1) + get(i, j, 0, 1) >= 4 || get(i, j, 1, 0) + get(i, j, -1, 0) >= 4 ||
get(i, j, -1, -1) + get(i, j, 1, 1) >= 4 || get(i, j, -1, 1) + get(i, j, 1, -1) >= 4) {
std::cout << "YES\n";
return;
}
}
}
}
std::cout << "NO\n";
}
C. Multi-judge Solving
题意:开始有一个\(k\),和一个数组\(a\),你每次可以拿\(a_i \leq 2k\)的\(i\),然后你的\(k\)会与拿的数取最大值,你也可以拿\(a\)里没有的数,花费1的代价。求把\(a\)里数都拿完的最小代价。
判断后从小到大拿,同时更新\(k\),遇到拿不到的就一直乘二到可以拿为止。
点击查看代码
void solve() {
i64 n, k;
std::cin >> n >> k;
std::vector<i64> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::sort(a.begin(), a.end());
int ans = 0;
for (int i = 0; i < n; ++ i) {
if (a[i] <= 2 * k) {
k = std::max(k, a[i]);
} else {
++ ans;
k = 2 * k;
-- i;
}
}
std::cout << ans << "\n";
}
D. Suitable Replacement
题意:给你两个字符串\(s, t\),\(s\)有些地方没有填,你把这些地方填上后进行重排后使得\(t\)出现的次数最多。
因为可以重排,那么我们想让\(t\)出现的最多只需要让对应字母出现的次数最多就行了。
考虑二分,如果我们需要\(mid\)个\(t\),那么每次\(t\)的字符需要出现\(cnt_i \times mid\)次。判断不够的是不是小于等于未填的位置个数。
点击查看代码
void solve() {
std::string s, t;
std::cin >> s >> t;
int n = s.size();
int k = std::count(s.begin(), s.end(), '?');
std::vector<int> cntt(26), cnts(26);
for (auto & c : s) {
if (c != '?') {
++ cnts[c - 'a'];
}
}
for (auto & c : t) {
++ cntt[c - 'a'];
}
auto check = [&](int m) -> bool {
int sum = 0;
for (int i = 0; i < 26; ++ i) {
sum += std::max(0, cntt[i] * m - cnts[i]);
}
return sum <= k;
};
int l = 0, r = n / (int)t.size();
while (l < r) {
int mid = l + r + 1 >> 1;
if (check(mid)) {
l = mid;
} else {
r = mid - 1;
}
}
std::stack<char> stk;
for (int i = 0; i < 26; ++ i) {
int sum = std::max(0, cntt[i] * l - cnts[i]);
while (sum -- ) {
stk.push(char(i + 'a'));
}
}
while (stk.size() < k) {
stk.push('a');
}
for (auto & c : s) {
if (c == '?') {
c = stk.top();
stk.pop();
}
}
std::cout << s << "\n";
}
E. Minimal Labels
题意:一个有向无环图,你要构造一个排列\(p\),使得如果\(u\)向\(v\)有边,则\(p_u < p_v\)。且排列的字典序最小。
没有入度的点一定使填最大的数,同时我们需要字典序最小,那么把这些点从大到小填。同时发现填完一个数后就会多出一些入度为0的点,然后可以填的数减一。这时就变成了一个子问题。于是我们用大根堆模拟拓扑排序,先出队的点填大的数。
点击查看代码
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<std::vector<int>> adj(n);
std::vector<int> in(n);
for (int i = 0; i < m; ++ i) {
int u, v;
std::cin >> u >> v;
-- u, -- v;
adj[v].push_back(u);
++ in[u];
}
std::vector<int> ans(n);
std::priority_queue<int> heap;
for (int i = 0; i < n; ++ i) {
if (!in[i]) {
heap.push(i);
}
}
int num = n;
while (heap.size()) {
int u = heap.top(); heap.pop();
ans[u] = num -- ;
for (auto & v : adj[u]) {
if ( -- in[v] == 0) {
heap.push(v);
}
}
}
for (int i = 0; i < n; ++ i) {
std::cout << ans[i] << " \n"[i == n - 1];
}
}
F. String Compression
题意:对于一个字符串可以表示为若干个\(cnt_i, s_i\),表示连续出现了\(cnt_i\)个\(s_i\),明显一个字符串有多种表现形式,你需要求出长度最小的表示。
对于一个字符串\(s\),对它进行\(kmp\),那么如果\(n \% (n - next[i]) == 0\),则有一个长度为\(n - next[i]\)的循环。那么我们可以枚举\(j\),做\([j, n]\)的\(kmp\),然后对于每个\(i\)看有没有循环。\(dp\)求解。
点击查看代码
void solve() {
std::string s;
std::cin >> s;
int n = s.size();
s = " " + s;
const int inf = 1e9;
std::vector<int> f(n + 1, inf);
f[0] = 0;
std::vector<int> cnt(n + 1);
for (int i = 1; i <= n; ++ i) {
cnt[i] = cnt[i / 10] + 1;
f[i] = i + 1;
}
std::vector<int> next(n + 1);
for (int k = 1; k <= n; ++ k) {
next[k] = k - 1;
for (int i = k + 1, j = k - 1; i <= n; ++ i) {
while (j >= k && s[i] != s[j + 1]) {
j = next[j];
}
j += s[i] == s[j + 1];
next[i] = j;
}
for (int i = k; i <= n; ++ i) {
int len = i - k + 1;
if (next[i] >= k && len % (len - (next[i] - k + 1)) == 0) {
f[i] = std::min(f[i], f[k - 1] + cnt[len / (len - (next[i] - k + 1))] + (len - (next[i] - k + 1)));
}
f[i] = std::min(f[i], f[k - 1] + 1 + len);
}
}
std::cout << f[n] << "\n";
}
G. Tree Queries
题意:给你一棵树,开始每个点都是黑色,\(q\)次操作,每次把一个点染成黑色,或者问你一个点到所有黑点的路径中编号最小的点。强制在线。
可以先把第一个黑色当作根,然后一遍\(dfs\)求出每个点到根的路径上编号最小的点。那么当我们染一个点的时候,就相当于拿它到根的最小编号更新答案。
点击查看代码
void solve() {
int n, q;
std::cin >> n >> q;
std::vector<std::vector<int>> adj(n + 1);
for (int i = 1; i < n; ++ i) {
int u, v;
std::cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
std::vector<int> f(n + 1, n);
auto dfs = [&](auto self, int u, int fa) -> void {
f[u] = std::min(f[u], u);
for (auto & v : adj[u]) {
if (v == fa) {
continue;
}
f[v] = f[u];
self(self, v, u);
}
};
int t, u;
std::cin >> t >> u;
u = u % n + 1;
dfs(dfs, u, 0);
int ans = u;
-- q;
int last = 0;
while (q -- ) {
std::cin >> t >> u;
u = (u + last) % n + 1;
if (t == 1) {
ans = std::min(ans, f[u]);
} else {
last = std::min(ans, f[u]);
std::cout << last << "\n";
}
}
}

浙公网安备 33010602011771号