Educational Codeforces Round 183 (Rated for Div. 2) A~E

A - Candies for Nephews

模拟。

\(3\) 的余数。

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

using namespace std;

using i64 = long long;

void solve() {

	int n;
	std::cin >> n;

	std::cout << (3 - n % 3) % 3 << "\n";

}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int t;
	cin >> t;
	while (t--) {
		solve();
	}

	return 0;
}

B - Deck of Cards

模拟。

手玩可以发现,\(0/1\) 的顺序无所谓,对两边的影响是固定的,除开这些后,\(2\) 会影响剩余的数的两边,当 \(cnt_2 * 2 \ge n\) 的时候,中间全都不确定,否则就左右两边影响 \(cnt_2\) 个。

注意 \(k=n\) 特判,以及是 \(1\) 在上面,所以去掉上面的是从 \(1\) 开始,不是 \(n\)

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

using namespace std;

using i64 = long long;

void solve() {

	int n, k;
	std::cin >> n >> k;

	std::string s;
	std::cin >> s;

	if (k == n) {
		std::cout << std::string(n, '-') << "\n";
		return;
	}

	int cnt[3] {};
	for (int i = 0; i < k; i += 1) {
		cnt[s[i] - '0'] += 1;
	}

	std::string ans = "";
	if (cnt[0]) {
		ans += std::string(cnt[0], '-');
	}

	n -= cnt[0] + cnt[1];
	if (2 * cnt[2] >= n) {
		ans += std::string(n, '?');
	} else {
		ans += std::string(cnt[2], '?');
		ans += std::string(n - 2 * cnt[2], '+');
		ans += std::string(cnt[2], '?');
	}

	if (cnt[1]) {
		ans += std::string(cnt[1], '-');
	}

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

}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int t;
	cin >> t;
	while (t--) {
		solve();
	}

	return 0;
}

C - Monocarp's String

前缀和,哈希。

\(a/b\) 视为 \(+1/-1\),那么问题就是问删除一个区间使得数组总和为 \(0\)

定义 \(a_i\)\(a/b\) 的贡献,若 \(\sum_{i=1}^na_i = d\),说明我们要找一段区间和等于 \(d\) 的删掉;定义 \(pre_i\) 表示前 \(i\) 个数的和,用哈希表 \(mp\) 记录每个 \(pre_i\) 的下标,如果 \(pre_i-d\) 出现过,说明 \(i-mp[pre_i-d] + 1\) 这一段就是要删掉的,比较一下取最小值即可。

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

using namespace std;

using i64 = long long;

void solve() {

	int n;
	std::cin >> n;

	std::string s;
	std::cin >> s;

	int cnt[2] {};
	for (int i = 0; i < n; i += 1) {
		int c = s[i] - 'a';
		cnt[c] += 1;
	}

	int d = cnt[0] - cnt[1];

	map<int, int> mp;
	mp[0] = 0;

	int ans = n, sum = 0;
	for (int i = 0; i < n; i += 1) {
		if (s[i] == 'a') {
			sum += 1;
		} else {
			sum -= 1;
		}

		if (mp.count(sum - d)) {
			ans = min(ans, i - mp[sum - d] + 1);
		}
		mp[sum] = i + 1;
	}

	if (ans == n) {
		ans = -1;
	}
	if (d == 0) {
		ans = 0;
	}

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

}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int t;
	cin >> t;
	while (t--) {
		solve();
	}

	return 0;
}

D. Inversion Value of a Permutation

\(dp\)

要求构造一个长度为 $ n $ 的排列,使得其逆序值恰好为 $ k $。逆序值定义为至少包含一个逆序对的连续子数组的数量。实际上,总子数组数为 $ \frac{n(n+1)}{2} $,减去没有逆序对的子数组数(即递增连续子数组数)即为逆序值。因此,问题转化为构造一个排列,使其递增连续子数组数 $ S = \frac{n(n+1)}{2} - k $。

若 $ S $ 无法表示为若干段长度 $ l_i $ 的平方和的一半之和(即 $ \sum \frac{l_i(l_i+1)}{2} = S $,其中 $ \sum l_i = n $),则输出 \(0\)。否则,将排列分成若干连续递增的段,段间递减排列即可。具体构造时,从大到小依次分配段内数字,每段内数字递增。

设 $ dp[x][i][j] $ 表示长度为 $ x $ 的排列中能否用若干段覆盖 $ i $ 个元素且递增子数组数为 $ j $,转移方程为:

\[dp[x][i+l][j + \frac{l(l+1)}{2}] \leftarrow dp[x][i][j] \quad \text{for } l \leq x-i \]

然后根据预处理结果还原方案即可。

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

using namespace std;

using i64 = long long;

const int N = 30;
bool dp[N + 1][N + 1][500];
int len[N + 1][N + 1][500], lst[N + 1][N + 1][500];

void solve() {

	int n, k;
	std::cin >> n >> k;

	int st = n * (n + 1) / 2 - k;

	if (!dp[n][n][st]) {
		std::cout << 0 << "\n";
	} else {
		std::vector<int> has;
		int i = n;
		while (i > 0) {
			int l = len[n][i][st];
			has.push_back(l);
			st = lst[n][i][st];
			i -= l;
		}

		std::reverse(has.begin(), has.end());
		std::vector<int> ans;

		int now = 0;
		for (auto &d : has) {
			int l = n - now - d + 1;
			int r = n - now;
			for (int num = l; num <= r; num++) {
				ans.push_back(num);
			}
			now += d;
		}

		for (int i = 0; i < n; i += 1) {
			std::cout << ans[i] << " \n"[i + 1 == n];
		}
	}

}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	for (int x = 1; x <= N; x++) {
		int y = x * (x + 1) / 2;
		dp[x][0][0] = true;
		for (int i = 0; i < x; i++) {
			for (int j = 0; j <= y; j++) {
				if (!dp[x][i][j]) continue;
				for (int l = 1; l <= x - i; l++) {
					int ni = i + l;
					int nj = j + l * (l + 1) / 2;
					if (nj <= y) {
						dp[x][ni][nj] = true;
						len[x][ni][nj] = l;
						lst[x][ni][nj] = j;
					}
				}
			}
		}
	}

	int t;
	cin >> t;
	while (t--) {
		solve();
	}

	return 0;
}

E. Predicting Popularity

数据结构。

对于每个用户可以得到 \(p_i = \max(a_i-ac,0)+\max(d_i-dr,0)\),即满足该用户的最小受欢迎度。

如果当前电影观看人数为 \(p\),说明至少有 \(p\)\(p_i< p\),一种方法显然是枚举 \(p\),去看 \(\sum [p_i< p]\) 的个数,反过来看,就是 \(p_i\)\(p_i + 1\) 及以上的答案都有贡献;记 \(cnt_p\) 为满足每个 \(p\)\(p_i<p\) 的个数,那么当 \(cnt_p \ge p\) 时,这个 \(p\) 就有可能是答案,为什么是有可能,因为要满足 \(p\) 是答案,那么前面的 \(1\sim p-1\) 都应该满足 \(cnt_i\ge i\),因此我们要的是最小的不满足 \(cnt_i\ge i\) 的位置,也就是 \(cnt_i-i<0\) 的第一个位置。

具体的,用线段树维护 \(cnt\) 数组,初始都减去一个 \(i\),每次 \(p_i\) 变化后就对相应的 \([p_i+1,n]\) 加减 \(1\),然后线段树上二分找第一个负数,因为答案可能为 \(0\),这里就整体偏移一位找第一个小于等于 \(0\) 的了。

参考[1]

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

using i64 = long long;

template<class Info, class Tag>
struct LazySegmentTree {
    int n;
    std::vector<Info> info;
    std::vector<Tag> tag;
    LazySegmentTree() : n(0) {}
    LazySegmentTree(int n_, Info v_ = Info()) {
        init(n_, v_);
    }
    template<class T>
    LazySegmentTree(std::vector<T> init_) {
        init(init_);
    }
    void init(int n_, Info v_ = Info()) {
        init(std::vector(n_, v_));
    }
    template<class T>
    void init(std::vector<T> init_) {
        n = init_.size();
        info.assign(4 << std::__lg(n), Info());
        tag.assign(4 << std::__lg(n), Tag());
        std::function<void(int, int, int)> build = [&](int p, int l, int r) {
            if (r - l == 1) {
                info[p] = init_[l];
                return;
            }
            int m = (l + r) / 2;
            build(2 * p, l, m);
            build(2 * p + 1, m, r);
            pull(p);
        };
        build(1, 0, n);
    }
    void pull(int p) {
        info[p] = info[2 * p] + info[2 * p + 1];
    }
    void apply(int p, const Tag &v) {
        info[p].apply(v);
        tag[p].apply(v);
    }
    void push(int p) {
        apply(2 * p, tag[p]);
        apply(2 * p + 1, tag[p]);
        tag[p] = Tag();
    }
    void modify(int p, int l, int r, int x, const Info &v) {
        if (r - l == 1) {
            info[p] = v;
            return;
        }
        int m = (l + r) / 2;
        push(p);
        if (x < m) {
            modify(2 * p, l, m, x, v);
        } else {
            modify(2 * p + 1, m, r, x, v);
        }
        pull(p);
    }
    void modify(int p, const Info &v) {
        modify(1, 0, n, p, v);
    }
    Info rangeQuery(int p, int l, int r, int x, int y) {
        if (l >= y || r <= x) {
            return Info();
        }
        if (l >= x && r <= y) {
            return info[p];
        }
        int m = (l + r) / 2;
        push(p);
        return rangeQuery(2 * p, l, m, x, y) + rangeQuery(2 * p + 1, m, r, x, y);
    }
    Info rangeQuery(int l, int r) {
        return rangeQuery(1, 0, n, l, r);
    }
    void rangeApply(int p, int l, int r, int x, int y, const Tag &v) {
        if (l >= y || r <= x) {
            return;
        }
        if (l >= x && r <= y) {
            apply(p, v);
            return;
        }
        int m = (l + r) / 2;
        push(p);
        rangeApply(2 * p, l, m, x, y, v);
        rangeApply(2 * p + 1, m, r, x, y, v);
        pull(p);
    }
    void rangeApply(int l, int r, const Tag &v) {
        return rangeApply(1, 0, n, l, r, v);
    }
    template<class F>
    int findFirst(int p, int l, int r, int x, int y, F &&pred) {
        if (l >= y || r <= x) {
            return -1;
        }
        if (l >= x && r <= y && !pred(info[p])) {
            return -1;
        }
        if (r - l == 1) {
            return l;
        }
        int m = (l + r) / 2;
        push(p);
        int res = findFirst(2 * p, l, m, x, y, pred);
        if (res == -1) {
            res = findFirst(2 * p + 1, m, r, x, y, pred);
        }
        return res;
    }
    template<class F>
    int findFirst(int l, int r, F &&pred) {
        return findFirst(1, 0, n, l, r, pred);
    }
    template<class F>
    int findLast(int p, int l, int r, int x, int y, F &&pred) {
        if (l >= y || r <= x) {
            return -1;
        }
        if (l >= x && r <= y && !pred(info[p])) {
            return -1;
        }
        if (r - l == 1) {
            return l;
        }
        int m = (l + r) / 2;
        push(p);
        int res = findLast(2 * p + 1, m, r, x, y, pred);
        if (res == -1) {
            res = findLast(2 * p, l, m, x, y, pred);
        }
        return res;
    }
    template<class F>
    int findLast(int l, int r, F &&pred) {
        return findLast(1, 0, n, l, r, pred);
    }
};

struct Tag {
    int add = 0;
    void apply(const Tag &t) {
        add += t.add;
    }
};

constexpr int inf = 1E9;

struct Info {
    int min = inf;
    void apply(const Tag &t) {
        min += t.add;
    }
};

Info operator+(const Info &l, const Info &r) {
    return {std::min(l.min, r.min)};
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int ac, dr;
    std::cin >> ac >> dr;

    int n;
    std::cin >> n;

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

    for(int i = 0; i < n; i += 1) {
        std::cin >> d[i];
    }

    LazySegmentTree<Info, Tag> seg(n + 1);
    for(int i = 0; i <= n + 1; i += 1) {
        seg.modify(i, {-i});
    }

    for(int i = 0; i < n; i += 1) {
        int p = std::max(a[i] - ac, 0) + std::max(d[i] - dr, 0);
        seg.rangeApply(p, n + 1, {1});
    }

    int m;
    std::cin >> m;

    while(m--) {
        int k, x, y;
        std::cin >> k >> x >> y;

        k--;
        int p = std::max(a[k] - ac, 0) + std::max(d[k] - dr, 0);
        seg.rangeApply(p, n + 1, {-1});
        a[k] = x, d[k] = y;
        p = std::max(a[k] - ac, 0) + std::max(d[k] - dr, 0);
        seg.rangeApply(p, n + 1, {1});

        auto res = seg.findFirst(0, n + 1, [](auto x){
            return x.min <= 0;
        });
        std::cout << res << "\n";
    }

    return 0;
}

  1. https://codeforces.com/blog/entry/147164 ↩︎

posted @ 2025-10-07 14:20  Ke_scholar  阅读(22)  评论(1)    收藏  举报