Educational Codeforces Round 183 (Rated for Div. 2) 补题记录
Educational Codeforces Round 183 (Rated for Div. 2) 补题记录
D - Inversion Value of a Permutation
题目大意:
排列的逆序值为这个排列中存在逆序对的子区间的个数。
构造一个 \(n\) 个数的排列,使排列的逆序值恰好等于 \(k\) 。
思路:
首先考虑如何计算答案。假设存在一组逆序对,\((p_i, p_j)\) \((1 \leq i, j \leq n)\) 其中 \(i + 1 = j\),我们可以发现这一组逆序对的贡献应该是 \(i\)。并且对于任意的逆序对 \((p_k, p_j)\) 其中 \(k < i\),他所代表的区间都已经被 \((p_i, p_j)\) 的贡献计算。因此我们得出结论:只需要统计相邻逆序对的贡献之和,即为排列的逆序值。
可以发现如果直接构造逆序值恰好为 \(k\) 的排列,难度很大。正难则反,我们可以考虑构造一个有 \(k' = \frac{n (n - 1)}{2} - k\) 个不存在逆序对的子区间的排列。
同时,我们知道:对于一个长度为 \(len\) 的连续递增子数组,他对 \(k'\) 的贡献为 \(\frac{len (len - 1)}{2}\)。因此便可以进一步转化为用 \(n\) 个数构造 \(m\) 个连续递增的子数组,其中第 \(i\) 个子数组的长度为 \(len_i\)。且存在:
接着我们再进一步考虑如何构造最终答案,其实很简单。对于一个排列 \(p = {1, 2, \dots, n}\) 我们只需要把它分成 \(m\) 组且符合上述等式,再将其倒序排列然后输出即可。
最后,我们要解决的就只剩下答案的存在性了。也就是我们能否用 \(n\) 个数组成一个存在 \(k'\) 个不存在逆序对的区间的排列。可以考虑用 \(dp\) 来预处理。用 \(dp[i][j]\) 表示使用 \(i\) 个数字构造 \(j\) 个连续子数组的可行性,那么转移方程便是:
其中,\(len\) 代表当前要构造一个长度为 \(len\) 的子数组。
code:
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define i64 long long
const int N = 40, M = N * (N - 1) / 2;
int f[N][M], pre[N][M];
void init() {
f[0][0] = 1;
for (int i = 1; i < N; i ++ ) {
for (int j = 0; j < M; j ++ ) {
for (int len = 1; i - len >= 0 && j - len * (len - 1) / 2 >= 0; len ++ ) {
if (f[i - len][j - len * (len - 1) / 2]) {
f[i][j] = 1;
pre[i][j] = len;
}
}
}
}
}
void MuBai() {
int n, k;
cin >> n >> k;
k = n * (n - 1) / 2 - k;
if (!f[n][k]) {
cout << "0\n";
return;
}
int nn = n, kk = k, last = 1;
vector<int> ans;
while (pre[nn][kk]) {
int len = pre[nn][kk];
for (int i = last + len - 1; i >= last; i -- ) {
ans.emplace_back(i);
}
last = last + len;
nn -= len, kk -= len * (len - 1) / 2;
}
for (int i = n - 1; i >= 0; i -- ) {
cout << ans[i] << " \n"[i == 0];
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
init();
int t; cin >> t;
while (t -- ) MuBai();
return 0;
}
E - Predicting Popularity
题目大意:
给出两个整数 \(ac\) 和 \(dr\) 表示一部电影的动作和剧情评分。
有 \(n\) 个观众,第 \(i\) 个观众的动作偏好为 \(a_i\) ,剧情偏好为 \(d_i\) ,假设现已观看电影的人数为 \(p\)。当满足如下条件时,第 \(i\) 位观众才会观看电影:
接着,有 \(m\) 次修改,第 \(i\) 次修改会改变第 \(k_i\) 个人的偏好,问每次修改过后最多有多少人会观看电影。
思路:
首先考虑如何计算答案:
由题意可知,一位观众只有当 \(max(a - ac, 0) + max(d - dr, 0) <= p\) 时才会观看电影。
同时,我们定义 \(cnt[i]\) 为当 \(p = i\) 时,观看电影的观众数量。容易得知,当 \(cnt[i] < i\) 时,\(p \geq i\) 都是无效的答案。
因此只需要找到从 \(i = 1\) 开始最长的 \(cnt[i] \geq i\) 的前缀长度便是所求答案。
然后考虑如何维护修改:
对于上述所求可以变式为维护 \(cnt[i] - i\) 的值,那么 \(cnt[i] - i \geq 0\) 的最大前缀长度便是答案。
假设一位观众的阈值为 \(x = max(a - ac, 0) + max(d - dr, 0)\) 那么他对于 \([cnt[x], cnt[inf]]\) 都存在贡献。因此修改一位观众的阈值,需要减去区间 \([x, inf)\) 的贡献,再在新的贡献区间 \([x_, inf)\) 加上贡献。
区间修改,考虑线段树维护。
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define i64 long long
#define lson node << 1
#define rson node << 1 | 1
template <class Info, class Tag>
struct LazySegmentTree {
struct Node {
int l, r;
Info info;
Tag tag;
};
vector<Node> tree;
LazySegmentTree(int n) {
init(n);
}
void init(int n) {
tree.resize(4 << __lg(n));
function<void(int, int, int)> build = [&](int node, int l, int r) {
Node& cur = tree[node];
cur.l = l, cur.r = r;
if (l == r) {
cur.info.mi = -l;
return;
}
int mid = (l + r) >> 1;
build(lson, l, mid);
build(rson, mid + 1, r);
maintain(node);
};
build(1, 0, n - 1);
}
void maintain(int node) {
Node& cur = tree[node];
cur.info = tree[lson].info + tree[rson].info;
}
void apply(int node, const Tag& tag) {
Node& cur = tree[node];
cur.info = cur.info + tag;
cur.tag = cur.tag + tag;
}
void spread(int node) {
Node& cur = tree[node];
if (!cur.tag.isTag()) {
return;
}
apply(lson, cur.tag);
apply(rson, cur.tag);
cur.tag.clear();
}
void rangeApply(int node, int l, int r, const Tag& tag) {
Node& cur = tree[node];
if (l <= cur.l && cur.r <= r) {
return apply(node, tag);
}
spread(node);
int mid = (cur.l + cur.r) >> 1;
if (l <= mid) {
rangeApply(lson, l, r, tag);
}
if (r > mid) {
rangeApply(rson, l, r, tag);
}
maintain(node);
}
void rangeApply(int l, int r, const Tag& tag) {
rangeApply(1, l, r, tag);
}
template <class T>
int findFirst(int node, T&& check) {
Node& cur = tree[node];
if (!check(cur.info)) {
return -1;
}
if (cur.l == cur.r) {
return cur.l;
}
spread(node);
int res = findFirst(lson, check);
if (res == -1) res = findFirst(rson, check);
return res;
}
template <class T>
int findFirst(T&& check) {
return findFirst(1, check);
}
};
struct Info {
int mi = INT_MAX;
};
struct Tag {
int add = 0;
bool isTag() { return add != 0; }
void clear() { add = 0; }
};
Info operator + (const Info& l, const Info& r) {
return { min(l.mi, r.mi) };
}
Info operator + (const Info& info, const Tag& tag) {
return { info.mi + tag.add };
}
Tag operator + (const Tag& x, const Tag& y) {
return { x.add + y.add };
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int ac, dr, n, m;
cin >> ac >> dr >> n;
vector<int> a(n + 1), d(n + 1), t(n + 1);
for (int i = 1; i <= n; i ++ ) cin >> a[i];
for (int i = 1; i <= n; i ++ ) {
cin >> d[i];
t[i] = max(a[i] - ac, 0) + max(d[i] - dr, 0);
}
LazySegmentTree<Info, Tag> seg_tree(n + 1);
for (int i = 1; i <= n; i ++ ) {
if (t[i] <= n) seg_tree.rangeApply(t[i], n, {1});
}
cin >> m;
for (int i = 1, p; i <= m; i ++ ) {
cin >> p >> a[p] >> d[p];
if (t[p] <= n) seg_tree.rangeApply(t[p], n, {-1});
t[p] = max(a[p] - ac, 0) + max(d[p] - dr, 0);
if (t[p] <= n) seg_tree.rangeApply(t[p], n, {1});
cout << seg_tree.findFirst([&](const Info& p) {
return p.mi <= 0;
}) << endl;
}
return 0;
}
F - Long Journey
题目大意:
给定两个整数 \(n\) 和 \(m\),以及两个数组 \(a,b\)。
在 \([0, m]\) 的条形区间中,当回合 \(i + kn,k \in [0, 1, 2 \dots ]\) 结束时,所有符合 \(x\) \(mod\) \(a_i \equiv b_i\) 的单位格将触发陷阱。
在不踩到陷阱的前提下,至少需要多少步才能从 \(0\) 走到 \(m\)?
思路:
先考虑模型转化,每回合先行动(走一格或停在本格),再按照规则激活陷阱。注意到,第 \(x\) 回合应用的规则只与 \(x \ mod \ n\) 有关,因此可以把回合数压缩到 \([0, n)\) 的区间进行处理。
而第 \(i\) 格在第 \(x\) 回合是否可达取决于 \(i\) 对 \(a[x \ mod \ n]\) 的余数,因此让 \(i\) 对 \(lcm(a[0], a[1], \dots, a[n - 1])\) 取模就可以包含所有信息。此时:
我们发现,题目中的 \(m \leq 10^{18}\) 数据非常大,因此考虑用倍增来拼凑这个 \(m\),我们用 \(dp[i][j][p]\) 来表示当前应用 \(i = x \ mod \ n\) 条规则,位于 \(j \ mod \ lcm\) 单位格 \(2^p\) 单位时间后最多能走的步数。存在如下转移:
最后,只需要特判 \(dp[0][0][maxP - 1] > m\) 是否成立然后计算答案即可。
code:
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define i64 long long
const int N = 15, M = 50, LCM = 2520;
i64 dp[N][LCM][M];
void MuBai() {
i64 n, m;
cin >> n >> m;
vector<i64> a(n), b(n);
for (int i = 0; i < n; i ++ ) cin >> a[i];
for (int i = 0; i < n; i ++ ) cin >> b[i];
for (int i = 0; i < n; i ++ ) {
for (int j = 0; j < LCM; j ++ ) {
if ((j + 1) % a[i] != b[i]) {
dp[i][j][0] = 1;
}
else {
dp[i][j][0] = 0;
}
}
}
for (int p = 1; p < M; p ++ ) {
for (int i = 0; i < n; i ++ ) {
for (int j = 0; j < LCM; j ++ ) {
dp[i][j][p] = dp[i][j][p - 1] + dp[(i + (1ll << (p - 1))) % n][(j + dp[i][j][p - 1]) % LCM][p - 1];
}
}
}
if (dp[0][0][M - 1] < m) {
cout << "-1\n";
return;
}
i64 x = 0, y = 0;
for (int p = M - 1; p >= 0; p -- ) {
if (y + dp[x % n][y % LCM][p] < m) {
y += dp[x % n][y % LCM][p];
x += (1ll << p);
}
}
cout << x + 1 << endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int t; cin >> t;
while (t -- ) MuBai();
return 0;
}

浙公网安备 33010602011771号