The 2024 ICPC Asia Nanjing Regional Contest
外榜 https://board.xcpcio.com/icpc/49th/nanjing
铜线: 3题406-137 ~ 4题607
银线: 4题606-357 ~ 5题650
金线: 5题645-467 6题+
难度(个人感觉):
纯签: E
半签: J
Easy: K
Mid: GB
E
题意
一个字符串循环左移(第一个放到最后)最多\(k\)次,求包含子串"nanjing"的最大数量。
思路
水题,暴力即可。
代码
cpp
void solve() {
int n, k;
cin >> n >> k;
string s;
cin >> s;
auto find = [&] (string s) -> int {
int ans = 0;
for (int i = 6; i < n; i++) {
string str = s.substr(i - 6, 7);
if (str == "nanjing") {
ans++;
}
}
return ans;
};
int ans = find(s);
for (int i = 0; i < std::min(7, k); i++) {
char x = s[0];
std::reverse(all(s));
s.pop_back();
std::reverse(all(s));
s.push_back(x);
ans = std::max(ans, find(s));
}
cout << ans << "\n";
}
J
题意
一共\(k\)个人,你与\(n\)个是好友。
有\(m\)条消息,每条消息和某两个人相关,如果你和这两个人都是好友,就能看到这条消息。
最多可以添加两个新好友,求能看到的最多的消息数量。
思路
要么选两个之间有消息的,要么直接选两个相关消息最多的。
代码
cpp
void solve() {
int n, m, k;
cin >> n >> m >> k;
vector<bool> a(k + 1);
for (int i = 0; i < n; i++) {
int x;
cin >> x;
a[x] = 1;
}
vector<int> cnt(k + 1);
int ans = 0;
std::map<std::pair<int, int>, int> mp;
for (int i = 0; i < m; i++) {
int x, y;
cin >> x >> y;
if (a[x] && a[y]) {
ans++;
} else if (a[x]) {
cnt[y]++;
} else if (a[y]) {
cnt[x]++;
} else {
if (x == y) {
cnt[x]++;
} else if (x > y) {
std::swap(x, y);
}
mp[{x, y}]++;
}
}
int add = 0;
for (auto [o, s] : mp) {
auto [x, y] = o;
add = std::max(add, s + (x != y) * (cnt[x] + cnt[y]));
}
std::sort(all(cnt), std::greater<>() );
add = std::max(add, cnt[0] + cnt[1]);
cout << ans + add << "\n";
}
K
题意
一共\(w \le 10^9\)个格子,其中\(n\)个红色,\(m\)个黑色,其他都是白色。
使用长度为\(k\)的带子覆盖所有红色,且不能覆盖到黑色,每个格子最多覆盖一次。
求需要的最少带子的数量,或者不存在方案。
思路
用黑色把w个格子分成若干个区间,在每个区间内去覆盖,带子不能超出区间。
对于每个区间,先以最左端的红色为起点,依次向右覆盖,最后一个超出区间,就需要向左移,和前一个重叠了,前一个也需要向左移,依此类推,如果最左端的也超出区间,则不存在方案。
算是个模拟吧。
代码
cpp
void solve() {
int n, m, k, w;
cin >> n >> m >> k >> w;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
vector<int> b(m);
for (int i = 0; i < m; i++) {
cin >> b[i];
}
b.push_back(0);
b.push_back(w + 1);
std::sort(all(a));
std::sort(all(b));
std::vector<std::pair<int, int>> seg;
for (int i = 1; i < b.size(); i++) {
seg.push_back({b[i - 1] + 1, b[i] - 1});
}
int ans = 0;
vector<int> res;
for (auto [l, r] : seg) {
int x = std::lower_bound(all(a), l) - a.begin();
int y = std::lower_bound(all(a), r + 1) - a.begin() - 1;
if (x >= n || x > y) {
continue;
}
if (r - l + 1 < k) {
cout << -1 << "\n";
return;
}
vector<std::pair<int, int>> seg2;
int tail;
for (int i = x; i <= y; i++) {
if (i == x) {
tail = a[i] + k - 1;
seg2.push_back({a[i], tail});
} else if (a[i] > tail) {
tail = a[i] + k - 1;
seg2.push_back({a[i], tail});
}
}
if (seg2.back().second > r) {
auto &[L, R] = seg2.back();
int del = R - r;
L -= del, R -= del;
for (int i = seg2.size() - 2; i >= 0; i--) {
if (seg2[i].second >= seg2[i + 1].first) {
int del = seg2[i].second - seg2[i + 1].first + 1;
seg2[i].first -= del;
seg2[i].second -= del;
}
}
if (seg2[0].first < l) {
cout << -1 << "\n";
return;
}
}
for (auto [L, R] : seg2) {
res.push_back(L);
}
ans += seg2.size();
}
cout << ans << "\n";
for (auto i : res) {
cout << i << " ";
}
cout << "\n";
}
G
题意
- 交互
一棵\(n\)节点的二叉树,最多可以\(p\)次询问,\(2^p \le n\)。找出一个事先设定的特殊点。
cpp
int query(int u, int v) { // 每次询问两个节点
std::cout << "? " << u << " " << v << std::endl;
int t;
std::cin >> t;
// t = 0 特殊点离 u 近
// t = 1 特殊点与 u v 的距离相等
// t = 2 特殊点离 v 近
// 距离表示简单路径上的边数
return t;
}
思路
\(log_2n\)次查询,明显二分。vp时写了2小时二分,写了一坨没做出来。
看官方题解发现,要以树的重心为分界点走分支。每次查询重心子树最大的两个子节点,离重心近则分离两个子树,否则分离除了离得近的子树的所有节点。
代码
cpp
void solve() {
int n;
cin >> n;
vector<vector<int>> e(n + 1);
for (int i = 1; i <= n; i++) {
int x, y;
cin >> x >> y;
if (x) {
e[i].push_back(x);
e[x].push_back(i);
}
if (y) {
e[i].push_back(y);
e[y].push_back(i);
}
}
auto query = [&] (int u, int v) -> int {
cout << "? " << u << " " << v << std::endl;
int t;
cin >> t;
return t;
};
vector<bool> del(n + 1);
vector<int> size(n + 1), max(n + 1);
auto dfs = [&] (auto &&dfs, int root) -> void {
int g;
auto find = [&] (auto &&find, int x, int fa) -> void {
size[x] = 1;
max[x] = 0;
for (auto u : e[x]) {
if (u != fa && !del[u]) {
find(find, u, x);
size[x] += size[u];
max[x] = std::max(max[x], size[u]);
}
}
max[x] = std::max(max[x], n - size[x]);
if (max[x] <= n / 2) {
g = x;
}
};
find(find, root, 0);
find(find, g, 0);
vector<std::pair<int, int>> son;
for (auto u : e[g]) {
if (!del[u]) {
son.push_back({size[u], u});
}
}
std::sort(all(son), std::greater<> ());
if (son.empty()) {
cout << "! " << g << std::endl;
return;
}
int u = g, v = 0;
if (son.size() == 1) {
v = son[0].second;
int t = query(u, v);
if (t == 0) {
cout << "! " << u << std::endl;
} else {
cout << "! " << v << std::endl;
}
} else {
u = son[0].second, v = son[1].second;
int t = query(u, v);
if (t == 0) {
del[g] = 1;
n = size[u];
dfs(dfs, u);
} else if (t == 2) {
del[g] = 1;
n = size[v];
dfs(dfs, v);
} else {
del[u] = del[v] = 1;
n -= size[u] + size[v];
dfs(dfs, g);
}
}
};
dfs(dfs, 1);
}
B
题意
一个包含\(012\)三种字符的字符串,可以执行任意次操作,每次操作,可以将某个\(2\)改为\(0\)或\(1\),或者选择相邻的两个\(0\)或\(1\)删掉并拼接字符串。求字符串可以达到的最短长度。
思路
神秘题目。
若删除两个字符,一定是删除一个奇数位一个偶数位,且每个字符下标的奇偶性不会变。
将所有偶数位取反,那么删除操作就变成了删除相邻两个不同的,则最终字符串一定是全\(0\)或全\(1\)。
所以\(2\)就可以每次修改成当前数量少的那一个。
代码
cpp
void solve() {
string s;
cin >> s;
for (int i = 1; i < s.size(); i += 2) {
if (s[i] == '0') {
s[i] = '1';
} else if (s[i] == '1') {
s[i] = '0';
}
}
int s0 = std::count(all(s), '0');
int s1 = std::count(all(s), '1');
int s2 = std::count(all(s), '2');
if (s0 + s2 < s1) {
cout << s1 - (s0 + s2) << "\n";
} else if (s1 + s2 < s0) {
cout << s0 - (s1 + s2) << "\n";
} else {
cout << ((s0 + s1 + s2) & 1) << "\n";
}
}