2024 ICPC成都
The 2024 ICPC Asia Chengdu Regional Contest
外榜 https://board.xcpcio.com/icpc/49th/chengdu
铜线: 5题953-331 ~ 6题770
银线: 6题769-456 ~ 7题674
金线: 7题600-600 8题+
难度(个人感觉):
纯签: LJ
半签: GA
Easy: I
Mid: B
L
题意
有50%的数小于等于a,95%的数小于等于b,99%的数小于等于c。
输出构造的序列。
思路
可以50个a,95-50个b,99-95个c,100-99个大于c的。
没啥好说的,就是读题麻烦点。
代码
void solve() {
int a, b, c;
cin >> a >> b >> c;
int n = 100;
cout << n << '\n';
for (int i = 1; i <= 50; i++) {
cout << a << " ";
}
for (int i = 51; i <= 95; i++) {
cout << b << " ";
}
for (int i = 96; i <= 99; i++) {
cout << c << " ";
}
cout << c + 1 << '\n';
}
J
模拟
代码
void solve() {
int n, m, q;
cin >> n >> m >> q;
std::map<int, bool> vis;
std::vector<std::pair<int, int>> ans(m + 1);
for (int i = 0; i <= m; i++) {
ans[i] = {i, 0};
}
int now = 0, sum = m;
while (q--) {
int op, id, x;
cin >> op;
if (op == 1) {
cin >> x;
now = x;
vis.clear();
sum = m;
} else if (op == 2) {
cin >> id >> x;
if (!vis[id] && now == x) {
vis[id] = 1;
ans[id].second += sum--;
}
} else {
cin >> id >> x;
if (now == x) {
vis[id] = 1;
}
}
}
sort(ans.begin() + 1, ans.end(), [&] (auto x, auto y) {
return x.second == y.second ? x.first < y.first : x.second > y.second;
});
for (int i = 1; i <= m; i++) {
cout << ans[i].first << " " << ans[i].second << "\n";
}
}
G
题意
对数组\(a\)可以执行任意次操作,每次在\(i\)和\(i+1\)之间插入\(a_i \land a_{i+1}\)或者\(a_i \lor a_{i+1}\)或者\(a_i \oplus a_{i+1}\)。
求数组中最多可以有多少不同的数。
思路
位运算多次也没有太大的意义,最多两次异或可以产生\(0\)。
所以使劲往里插,相邻两个数中间插入的只会受这两个数的影响,把他俩中间能插的全插进去。
代码
void solve() {
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
std::set<int> set;
set.insert(0);
for (int i = 1; i < n; i++) {
int l = a[i], r = a[i + 1];
set.insert(l);
set.insert(r);
vector<int> res = {l ^ r, l & r, l | r};
for (int j = 0; j < 3; j++) {
res.push_back(l ^ res[j]);
res.push_back(l & res[j]);
res.push_back(l | res[j]);
res.push_back(r ^ res[j]);
res.push_back(r & res[j]);
res.push_back(r | res[j]);
}
for (auto x : res) {
set.insert(x);
}
}
cout << set.size() << endl;
}
A
题意
操作长度为\(n\)的空字符串,每次操作可以选择长度至少为5的子串,改成箭头串,问能不能\(n\)次操作内变成s,输出每次操作的区间或者不能。
箭头串: 长度至少为5,'>'开头,'>>>'结尾,中间都是'-'。
思路
开头不是'>'结尾不是">>>"或者全'>'的一定不能。
把后面连续的'>'用长度为5的填满。
从左往右,每次以'>'为开头到串尾第一次连续三个'>'为结尾进行操作。
代码
void solve() {
string s;
cin >> s;
int n = s.size();
int len = std::count(all(s), '>');
if (len == n || s[0] == '-' || s.substr(n - 3, 3) != ">>>") {
cout << "No\n";
return;
}
int p = n - 1;
for (int i = n - 1; i >= 2; i--) {
if (s.substr(i - 2, 3) == ">>>") {
p = i;
} else {
break;
}
}
cout << "Yes ";
vector<std::pair<int, int>> res;
for (int i = n - 1; i >= p; i--) {
res.push_back({i - 3, 5});
}
for (int i = 0; i < p - 2; i++) {
if (s[i] == '>') {
res.push_back({i + 1, p - i + 1});
}
}
cout << res.size() << '\n';
for (auto [l, r] : res) {
cout << l << " " << r << "\n";
}
}
I
题意
一种划分\(k\)是合法的,当且仅当划分成连续的\(\lceil \frac{n}{k} \rceil\)部分,每部分的长度为\(1 \le k \le n\)(最后一部分可以不够),且非递减。
\(q\)次修改,将\(a_p\)改成\(v\)。求每次修改后有多少种划分合法。
思路
如果对于划分x合法,那么对于x的因子也合法,就相当于把原来的一部分又继续细分了。
所以维护好断开位置下标的gcd,只要找到最长划分,答案就是因子数量。
代码
void solve() {
int n, q;
cin >> n >> q;
vector<int> a(n + 2);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
vector<int> fac(n + 1);
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j += i) {
fac[j]++;
}
}
fac[0] = n;
vector<int> tr(n << 2);
auto pull = [&] (int i) -> void {
tr[i] = std::gcd(tr[i << 1], tr[i << 1 | 1]);
};
auto modify = [&] (auto &&modify, int i, int l, int r, int p, int v) -> void {
if (l == r) {
tr[i] = v;
return;
}
int mid = l + r >> 1;
if (p <= mid)
modify(modify, i << 1, l, mid, p, v);
if (p > mid)
modify(modify, i << 1 | 1, mid + 1, r, p, v);
pull(i);
};
for (int i = 1; i < n; i++) {
if (a[i] > a[i + 1]) {
modify(modify, 1, 1, n, i, i);
} else {
modify(modify, 1, 1, n, i, 0);
}
}
cout << fac[tr[1]] << '\n';
while (q--) {
int p, v;
cin >> p >> v;
a[p] = v;
if (a[p - 1] > a[p]) {
modify(modify, 1, 1, n, p - 1, p - 1);
} else {
modify(modify, 1, 1, n, p - 1, 0);
}
if (p + 1 <= n) {
if (a[p] > a[p + 1]) {
modify(modify, 1, 1, n, p, p);
} else {
modify(modify, 1, 1, n, p, 0);
}
}
cout << fac[tr[1]] << '\n';
}
}
B
题意
有三种字符abc,要求构造长度为\(n\)的字符串,把原串里的'?'换成abc且相邻两个不相同,问能构造出多少种字符串。
\(q\)次询问,特殊的,每次给\(x,y,z\)表示最多可以额外有\(x\)个a,\(y\)个b,\(z\)个c,输出答案。
思路
因为\(n,x,y,z \le 300\),所以dp预处理出所有的对应xyz的情况。
这样每次查询就把dp累加统计起来就可以了,时间复杂度\(O(q \times xyz)\),不能接受,高维前缀和维护一下就可以每次\(O(1)\)查询。
代码
constexpr int mod = 1e9 + 7;
ll dp[305][305][305][3]; // 前 i 个位置 额外使用 j个a, k个b,当前位置使用字母 l+'a';
ll f[305][305][305];
void solve() {
int n, q;
cin >> n >> q;
string s;
cin >> s;
s = " " + s;
vector<int> pre(n + 1);
for (int i = 1; i <= n; i++) {
pre[i] = pre[i - 1] + (s[i] == '?');
}
if (s[1] == '?') {
dp[1][1][0][0] = 1;
dp[1][0][1][1] = 1;
dp[1][0][0][2] = 1;
} else {
dp[1][0][0][s[1] - 'a'] = 1;
}
for (int i = 2; i <= n; i++) {
for (int a = 0; a <= pre[i]; a++) {
for (int b = 0; a + b <= pre[i]; b++) {
if (s[i] == '?') {
if (a) dp[i][a][b][0] = dp[i - 1][a - 1][b][1] + dp[i - 1][a - 1][b][2];
if (b) dp[i][a][b][1] = dp[i - 1][a][b - 1][0] + dp[i - 1][a][b - 1][2];
if (pre[i] - a - b) dp[i][a][b][2] = dp[i - 1][a][b][0] + dp[i - 1][a][b][1];
} else {
dp[i][a][b][s[i] - 'a'] = dp[i - 1][a][b][0] + dp[i - 1][a][b][1] + dp[i - 1][a][b][2] - dp[i - 1][a][b][s[i] - 'a'];
}
}
}
for (int a = 0; a <= pre[i]; a++) {
for (int b = 0; a + b <= pre[i]; b++) {
for (int j = 0; j < 3; j++) {
dp[i][a][b][j] %= mod;
}
}
}
}
for (int a = 0; a <= pre[n]; a++) {
for (int b = 0; a + b <= pre[n]; b++) {
int c = pre[n] - a - b;
f[a][b][c] = (dp[n][a][b][0] + dp[n][a][b][1] + dp[n][a][b][2]) % mod;
}
}
for (int i = 0; i <= 300; i++) {
for (int j = 0; j <= 300; j++) {
for (int k = 0; k <= 300; k++) {
if (i) f[i][j][k] += f[i - 1][j][k];
if (j) f[i][j][k] += f[i][j - 1][k];
if (k) f[i][j][k] += f[i][j][k - 1];
if (i && j) f[i][j][k] -= f[i - 1][j - 1][k];
if (i && k) f[i][j][k] -= f[i - 1][j][k - 1];
if (j && k) f[i][j][k] -= f[i][j - 1][k - 1];
if (i && j && k) f[i][j][k] += f[i - 1][j - 1][k - 1];
f[i][j][k] %= mod;
}
}
}
while (q--) {
int x, y, z;
cin >> x >> y >> z;
cout << (f[x][y][z] + mod) % mod << "\n";
}
}