The 2024 ICPC Kunming Invitational Contest
The 2024 ICPC Kunming Invitational Contest
A - Two-star Contest
将其按 \(s\) 从小到大排序后,再从小到大遍历,那么对于当前比赛应尽量最小化分数之和。记录一下 \(s_j<s_i\) 的最大分数 \(x\),则当前比赛分数至少要是 \(x+1\),遍历每个 \(-1\),如果分数不够就填上。复杂度 \(O(nm+n\log n)\)。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int kMaxN = 4e5 + 10;
int s[kMaxN], id[kMaxN], t, n, m, k, flag;
vector<int> v[kMaxN];
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (cin >> t; t--; flag = 0) {
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) {
cin >> s[i], id[i] = i, v[i].clear(), v[i].resize(m);
for (int j = 0; j < m; j++) {
cin >> v[i][j];
}
}
sort(id + 1, id + 1 + n, [](int x, int y) { return s[x] < s[y]; });
for (int l = 1, r, lst = -1; l <= n; l = r + 1) {
for (r = l; r + 1 <= n && s[id[l]] == s[id[r + 1]]; r++);
int maxn = 0;
for (int i = l; i <= r; i++) {
int sum = lst + 1;
for (int j = 0; j < m; j++) {
(v[id[i]][j] != -1) && (sum -= v[id[i]][j]);
}
for (int j = 0; j < m; j++) {
(v[id[i]][j] == -1) && (sum -= (v[id[i]][j] = max(0ll, min(k, sum))));
}
flag |= sum > 0, maxn = max(maxn, lst + 1 - sum);
}
lst = maxn;
}
if (flag) {
cout << "No\n";
} else {
cout << "Yes\n";
for (int i = 1; i <= n; i++) {
for (int j = 0; j < m; j++) {
cout << v[i][j] << ' ';
}
cout << '\n';
}
}
}
return 0;
}
B - Gold Medal
按 \(a_i\bmod k\) 从大到小排序,优先满足大的,如果填完所有的还有剩余,那就除以 \(k\) 下取整加到答案上即可。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int kMaxN = 110;
int a[kMaxN], t, n, m, k, ans;
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (cin >> t; t--; ans = 0) {
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i], ans += a[i] / k, a[i] %= k;
}
cin >> m, sort(a + 1, a + 1 + n);
for (int i = n; i && k - a[i] <= m; i--) {
m -= k - a[i], ans++;
}
cout << ans + m / k << '\n';
}
return 0;
}
C - Stop the Castle 2
删除障碍不好考虑,所以转化为初始没有障碍,要在指定的 \(m\) 位置上选择 \(m-k\) 个位置加入障碍,要求中间有障碍的城堡对数最大。所以我们优先加入能使得两对城堡产生贡献的障碍,再加入能使得一对城堡产生贡献的障碍,最后若还有剩余就随便加入。所以将横向相邻的一对城堡看做左部点,纵向相邻的一对城堡看做右部点,那么一个障碍如果能使两对城堡产生贡献,就可以在这两对城堡中连边,所以最后的答案就是二分图最大匹配的结果。直接写一个网络流即可。
#include <bits/stdc++.h>
#define int long long
using namespace std;
namespace Network_Flow {
const int kMaxN = 2e5 + 10, kMaxM = 3e6 + 10;
struct Edge {
int v, w, nxt;
} e[kMaxM];
int h[kMaxN], cur[kMaxN], dep[kMaxN], s, t, siz, tot = 1;
void I() { tot = 1, memset(h, 0, sizeof h); }
void A(int x, int y, int z) { e[++tot] = {y, z, h[x]}, h[x] = tot; }
int L(int x, int y, int z) { return A(x, y, z), A(y, x, 0), tot - 1; }
bool B() {
memcpy(cur, h, sizeof cur), memset(dep, -1, sizeof dep);
queue<int> q;
for (q.push(s), dep[s] = 0; !q.empty();) {
int x = q.front();
q.pop();
for (int i = h[x]; i; i = e[i].nxt) {
(e[i].w && dep[e[i].v] == -1) && (dep[e[i].v] = dep[x] + 1, q.push(e[i].v), 0);
}
}
return ~dep[t];
}
int D2(int x, int num) {
if (x == t) {
return num;
}
int cnt = num;
for (int i = cur[x]; i; i = e[i].nxt) {
int y = e[cur[x] = i].v;
if (e[i].w && dep[y] == dep[x] + 1) {
int g = D2(y, min(cnt, e[i].w));
(!g) && (dep[y] = -1), e[i].w -= g, e[i ^ 1].w += g, cnt -= g;
}
if (!cnt) {
return num;
}
}
return num - cnt;
}
int D() {
int sum = 0;
for (; B(); sum += D2(s, 1e15));
return sum;
}
} // namespace Network_Flow
const int kMaxN = 2e5 + 10;
struct node {
int x, y, id;
} a[kMaxN];
int L[kMaxN], R[kMaxN], e[kMaxN], vis[kMaxN], vis2[kMaxN], vis3[kMaxN], t, n, m, K, cnt1, cnt2, cnt3;
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (cin >> t; t--; cnt1 = cnt2 = cnt3 = 0, cout << '\n') {
cin >> n >> m >> K, Network_Flow::I(), K = m - K;
for (int i = 1; i <= n; i++) {
cin >> a[i].x >> a[i].y, a[i].id = -1;
}
for (int i = 1; i <= m; i++) {
cin >> a[i + n].x >> a[i + n].y, a[i + n].id = i, L[i] = R[i] = vis[i] = 0;
}
sort(a + 1, a + 1 + n + m, [](node x, node y) { return x.x != y.x ? x.x < y.x : x.y < y.y; });
for (int i = 1, j; i <= n + m; i = j) {
for (j = i; j <= n + m && a[j].x == a[i].x; j++);
for (int k = i, p = 0; k < j; k++) {
if (a[k].id == -1) {
if (p && p != k - 1) {
cnt1++;
for (int o = p + 1; o <= k - 1; L[a[o].id] = cnt1, o++);
} else if (p) {
cnt3++;
}
p = k;
}
}
}
sort(a + 1, a + 1 + n + m, [](node x, node y) { return x.y != y.y ? x.y < y.y : x.x < y.x; });
for (int i = 1, j; i <= n + m; i = j) {
for (j = i; j <= n + m && a[j].y == a[i].y; j++);
for (int k = i, p = 0; k < j; k++) {
if (a[k].id == -1) {
if (p && p != k - 1) {
cnt2++;
for (int o = p + 1; o <= k - 1; R[a[o].id] = cnt2, o++);
} else if (p) {
cnt3++;
}
p = k;
}
}
}
int s = Network_Flow::s = cnt1 + cnt2 + 1, t = Network_Flow::t = Network_Flow::siz = s + 1;
for (int i = 1; i <= m; i++) {
(L[i] && R[i]) && (e[i] = Network_Flow::L(L[i], cnt1 + R[i], 1));
}
for (int i = 1; i <= cnt1; i++) {
Network_Flow::L(s, i, 1), vis2[i] = 0;
}
for (int i = 1; i <= cnt2; i++) {
Network_Flow::L(cnt1 + i, t, 1), vis3[i] = 0;
}
int w = Network_Flow::D();
if (K <= w) {
cout << cnt1 + cnt2 - 2 * K + cnt3 << '\n';
for (int i = 1; i <= m && K; i++) {
(L[i] && R[i] && !Network_Flow::e[e[i]].w) && (vis[i] = 1, K--);
}
} else {
cout << max(cnt1 + cnt2 - 2 * w - (K - w), 0ll) + cnt3 << '\n';
for (int i = 1; i <= m && K; i++) {
(L[i] && R[i] && !Network_Flow::e[e[i]].w) && (vis[i] = vis2[L[i]] = vis3[R[i]] = 1, K--);
}
for (int i = 1; i <= m && K; i++) {
if (!vis[i]) {
(L[i] && !vis2[L[i]]) && (vis[i] = vis2[L[i]] = 1, K--), (R[i] && !vis3[R[i]]) && (vis[i] = vis3[R[i]] = 1, K--);
}
}
for (int i = 1; i <= m && K; i++) {
(!vis[i]) && (vis[i] = 1, K--);
}
}
for (int i = 1; i <= m; i++) {
(!vis[i]) && (cout << i << ' ');
}
}
return 0;
}
D - Generated String
每个生成的字符串均由模板字符串的多个子串拼接而成。为了高效表示和比较这些字符串,我们采用哈希值来标识它们。针对查询操作中要求同时匹配前缀和后缀的需求,我们可以将这一问题转化为二维平面上的查询:将每个字符串的“前缀特征”作为横坐标,将其“后缀特征”作为纵坐标。如此一来,查询符合条件的字符串数量就等价于统计矩形区域内的点个数。
为了简化后缀匹配的处理,我们通过字符串反转将后缀匹配转化为前缀匹配,从而统一处理逻辑。在算法优化上,我们使用随机基数和模数进行字符串哈希,并预处理模板字符串及其反转的哈希值,以提升效率并减少碰撞。对于每个操作中涉及的字符串,我们计算其哈希值,并按照字典序对所有字符串进行排序,从而得到每个字符串的排名区间。
在此基础上,我们将添加和删除操作视为平面上的点,而查询操作则转化为矩形区域查询。最终,利用 CDQ 分治算法离线处理所有查询,高效地统计出结果即可。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int kMaxN = 2e5 + 10, kMod = 1e9 + 21;
mt19937 R(time(0));
int p[kMaxN], b[kMaxN], st[kMaxN], id[kMaxN], del[kMaxN], st1[kMaxN], ed1[kMaxN], st2[kMaxN], ed2[kMaxN], ans[kMaxN], n, m, tot, rnd;
char s[kMaxN], op[kMaxN];
int Q(int l, int r) { return (st[r] + (kMod - p[r - l + 1]) * st[l - 1]) % kMod; }
struct Segment_Tree {
int tr[kMaxN], st[kMaxN], tp, s;
void G(int x, int val) { for (st[++tp] = x; x <= m; tr[x] += val, x += x & -x); }
int Q(int x) {
for (s = 0; x; x &= x - 1) s += tr[x];
return s;
}
void I() {
for (; tp;) {
for (int x = st[tp--]; x <= m; tr[x] = 0, x += x & -x);
}
}
} tr;
struct node {
int x, l, r, id, k;
} a[kMaxN * 2];
struct String {
int len;
vector<int> L, R, h, siz;
void R1() {
int k;
cin >> k, L.resize(k), R.resize(k);
for (int i = 0; i < k; i++) {
cin >> L[i] >> R[i];
}
}
void R2() {
swap(L, R);
for (int &i : L) {
i = 2 * n + 1 - i;
}
for (int &i : R) {
i = 2 * n + 1 - i;
}
reverse(L.begin(), L.end()), reverse(R.begin(), R.end());
}
void I() {
int k = L.size();
len = 0, h.resize(k), siz.resize(k);
for (int i = 0; i < k; i++) {
len += R[i] - L[i] + 1, siz[i] = len, h[i] = ((i ? h[i - 1] : 0) * p[R[i] - L[i] + 1] + Q(L[i], R[i])) % kMod;
}
}
int Q1(int x) {
int i = lower_bound(siz.begin(), siz.end(), x) - siz.begin();
if (!i) {
return Q(L[i], L[i] + x - 1);
}
return x -= siz[i - 1], (h[i - 1] * p[x] + Q(L[i], L[i] + x - 1)) % kMod;
}
char Q2(int x) {
int i = lower_bound(siz.begin(), siz.end(), x) - siz.begin();
return x -= i ? siz[i - 1] : 0, s[L[i] + x - 1];
}
} s1[kMaxN], s2[kMaxN];
int L(String &u, String &v) {
int l = 1, r = min(u.len, v.len), x = 0;
for (; l <= r;) {
int mid = l + r >> 1;
(u.Q1(mid) == v.Q1(mid)) ? (x = mid, l = mid + 1) : (r = mid - 1);
}
return x;
}
void B(String *S, int *st, int *ed) {
int q = 0;
for (int i = 1; i <= m; i++) {
(op[i] != '-') && (id[++q] = i);
}
sort(id + 1, id + 1 + q, [&](int x, int y) {
int k = L(S[x], S[y]);
if (k == min(S[x].len, S[y].len)) {
return S[x].len != S[y].len ? S[x].len < S[y].len : op[x] == '?' && op[y] == '+';
}
return S[x].Q2(k + 1) < S[y].Q2(k + 1);
});
for (int i = 1; i <= q; i++) {
st[id[i]] = i;
int l = i + 1, r = q, k = i, e = S[id[i]].len, t = S[id[i]].h.back();
for (; l <= r;) {
int mid = l + r >> 1;
(S[id[mid]].len >= e && S[id[mid]].Q1(e) == t) ? (k = mid, l = mid + 1) : (r = mid - 1);
}
ed[id[i]] = k;
}
}
void C(int l, int r) {
if (l == r) {
return;
}
int mid = l + r >> 1;
C(l, mid), C(mid + 1, r), tr.I();
for (int i = mid + 1, j = l; i <= r; i++) {
for (; j <= mid && a[j].x <= a[i].x; j++) {
(!a[j].id) && (tr.G(a[j].l, a[j].k), 0);
}
(a[i].id) && (ans[a[i].id] += a[i].k * (tr.Q(a[i].r) - tr.Q(a[i].l - 1)));
}
inplace_merge(a + l, a + 1 + mid, a + 1 + r, [](node x, node y) { return x.x < y.x; });
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
rnd = R() % kMod, p[0] = 1;
for (int i = 1; i < kMaxN; i++) {
p[i] = p[i - 1] * rnd % kMod;
}
for (int i = 0; i < 26; i++) {
b[i] = R() % kMod;
}
cin >> n >> m >> (s + 1);
for (int i = 1; i <= n; i++) {
s[2 * n + 1 - i] = s[i];
}
for (int i = 1; i <= 2 * n; i++) {
st[i] = (st[i - 1] * rnd + b[s[i] - 'a']) % kMod;
}
for (int i = 1; i <= m; i++) {
cin >> op[i];
if (op[i] == '-') {
cin >> del[i];
} else if (op[i] == '?') {
s1[i].R1(), s2[i].R1(), s2[i].R2(), s1[i].I(), s2[i].I();
} else {
s1[i].R1(), s2[i] = s1[i], s2[i].R2(), s1[i].I(), s2[i].I();
}
}
B(s1, st1, ed1), B(s2, st2, ed2);
for (int i = 1; i <= m; i++) {
if (op[i] == '?') {
a[++tot] = {st1[i] - 1, st2[i], ed2[i], i, -1}, a[++tot] = {ed1[i], st2[i], ed2[i], i, 1};
} else {
a[++tot] = {st1[op[i] == '-' ? del[i] : i], st2[op[i] == '-' ? del[i] : i], 0, 0, op[i] == '-' ? -1 : 1};
}
}
C(1, tot);
for (int i = 1; i <= m; i++) {
(op[i] == '?') && (cout << ans[i] << '\n');
}
return 0;
}
E - Relearn through Review
考虑选中的区间是 \([l,r]\) 时,\(\gcd(a_1,a_2,\cdots,a_{l-1})=x,\gcd(a_{r+1},a_{r+1},\cdots,a_n)\),加入限制 \(a_l\nmid x,a_r\nmid y\),如果不满足这条限制,则区间向内缩一定不会改变答案。于是合法的前后缀必须是 \(\gcd\) 的切换点,这样的点位各有 \(\log V\) 个,对这 \(\log^2V\) 个位置求答案即可。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int kMaxN = 3e5 + 10;
int a[kMaxN], pre[kMaxN], suf[kMaxN], val[kMaxN], t, n, k, ans;
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (cin >> t; t--;) {
cin >> n >> k, pre[0] = suf[n + 1] = val[0] = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
pre[i] = __gcd(pre[i - 1], a[i]), val[i] = __gcd(val[i - 1], a[i] + k);
}
for (int i = n; i >= 1; i--) {
suf[i] = __gcd(suf[i + 1], a[i]);
}
ans = pre[n];
for (int i = 1; i <= n; i++) {
ans = max(ans, __gcd(val[i], suf[i + 1]));
}
for (int i = 2; i <= n; i++) {
val[i - 1] = 0;
for (int j = i; j <= n && __gcd(val[j - 1], a[j] + k) != val[j]; j++) {
val[j] = __gcd(val[j - 1], a[j] + k), ans = max(ans, __gcd(__gcd(val[j - 1], a[j] + k), __gcd(pre[i - 1], suf[j + 1])));
}
}
cout << ans << '\n';
}
return 0;
}
F - Collect the Coins
这道题一眼二分答案,然后考虑怎么判定。维护其中一个机器人完成了 \(i\) 任务,另一个机器人上一次完成的任务是 \(j\) 是否可行,转移需要在线段树上区间求一些东西的 \(\min\) 和 \(\max\)。
抛掉这个思路。对于 \(i\) 维护当前一个机器人 A 完成了 \(i\) 任务,另一个机器人 B 能在位置 \([l,r]\),接下来的转移将归纳地证明,这是一个区间。令 \(u=V(t_{i+1}-t_i)\)。
- 如果 \([l-u,r+u]\) 与 \([c_i-u,c_i+u]\) 都不包含 \(c_{i+1}\),那么无法完成任务。
- 如果只有 \([l-u,r+u]\) 包含 \(c_{i+1}\),那么让机器人 B 去完成该任务,\(l'=c_i-u,r'=c_i+u\)。
- 如果只有 \([c_i-u,c_i+u]\) 包含 \(c_{i+1}\),那么让机器人 A 去完成任务,\(l'=l-u,r'=r+u\)。
- 如果 \([l-u,r+u]\) 与 \([c_i-u,c_i+u]\) 都包含 \(c_{i+1}\),两者都可以去完成任务,新的区间是 \([l-u,r+u]\) 与 \([c_i-u,c_i+u]\) 的并,因为二者都包含 \(c_{i+1}\),所以它们的并也是区间。
所以二分可以线性判断,所以 \(O(n\log V)\) 就过了。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int kMaxN = 1e6 + 10;
int t[kMaxN], c[kMaxN], T, n;
int C(int x) {
int l1 = 1, r1 = 1e9, l2 = 1, r2 = 1e9;
for (int i = 1; i <= n; i++) {
l1 -= (t[i] - t[i - 1]) * x, l2 -= (t[i] - t[i - 1]) * x, r1 += (t[i] - t[i - 1]) * x, r2 += (t[i] - t[i - 1]) * x;
if ((l1 > c[i] || c[i] > r1) && (l2 > c[i] || c[i] > r2)) {
return 0;
} else if (l2 > c[i] || c[i] > r2) {
l1 = r1 = c[i];
} else if (l1 > c[i] || c[i] > r1) {
l2 = r2 = c[i];
} else {
l1 = min(l1, l2), r1 = max(r1, r2), l2 = r2 = c[i];
}
}
return 1;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (cin >> T; T--;) {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> t[i] >> c[i];
}
int l = 0, r = 2e9;
for (; l < r;) {
int mid = l + r >> 1;
(C(mid)) ? (r = mid) : (l = mid + 1);
}
cout << (l >= 2e9 ? -1 : l) << '\n';
}
return 0;
}
G - Be Positive
打表可以发现,当 \(n=1\) 或 \(n|4\) 时无解。非这种情况就将 \(i=1\) 或 \(i|4\) 的位置与下一个位置交换即可。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int t, n;
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (cin >> t; t--;) {
cin >> n;
if (n == 1 || !(n % 4)) {
cout << "impossible\n";
} else {
for (int i = 0; i < n; i++) {
if (!i || i % 4 == 3) {
cout << i + 1 << ' ' << i << ' ', i++;
} else {
cout << i << ' ';
}
}
cout << '\n';
}
}
return 0;
}
H - Subarray
这道题不难想到分治,我们把区间 \([l,r]\) 中的最大值将当前区间划分成若干个子段。所以不妨设这几段的长度为 \(a_1,a_2,\cdots,a_m\),所以贡献为:
所以直接反转然后 NTT 做卷积即可。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int kMod = 998244353;
namespace NTT {
int r[1 << 22], p;
int P(int x, int y) {
int z = 1;
for (; y; (y & 1) && (z = z * x % kMod), y >>= 1, x = x * x % kMod);
return z;
}
void N(int f[], int n, int inv) {
for ((!p) && (p = r[1] = 1); p < n;) {
p <<= 1;
int w = P(3, (kMod - 1) / p);
for (int i = p; i < p * 2; i += 2) {
r[i] = r[i / 2], r[i + 1] = r[i] * w % kMod;
}
}
for (int i = 1, j = 0; i < n; i++) {
((j ^= n - (n >> __builtin_ffs(i))) > i) && (swap(f[i], f[j]), 0);
}
for (int k = 1; k < n; k <<= 1) {
for (int i = 0; i < n; i += k * 2) {
for (int j = i, w = k * 2; j < i + k; j++, w++) {
int x = f[j], y = f[j + k] * r[w] % kMod;
f[j] = (x + y) % kMod, f[j + k] = (x - y + kMod) % kMod;
}
}
}
if (inv == -1) {
int w = P(n, kMod - 2);
reverse(f + 1, f + n);
for (int i = 0; i < n; i++) {
f[i] = f[i] * w % kMod;
}
}
}
vector<int> C(vector<int> f, vector<int> g) {
int n = 1 << __lg(f.size() + g.size() - 1) + 1;
f.resize(n), g.resize(n), N(f.data(), n, 1), N(g.data(), n, 1);
for (int i = 0; i < n; i++) {
f[i] = f[i] * g[i] % kMod;
}
return N(f.data(), n, -1), f;
}
} // namespace NTT
const int kMaxN = 4e5 + 10;
int sum[kMaxN], t, n, ans;
map<int, vector<int> > pos;
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (cin >> t; t--; pos.clear(), ans = 0) {
cin >> n;
set<int> s{0, n + 1};
for (int i = 1, x; i <= n; i++) {
cin >> x, pos[x].push_back(i), s.insert(i);
}
for (auto x : pos) {
vector<int> a = x.second;
for (int i = 0, j = 0; i < a.size(); i++) {
if (i == a.size() - 1 || *s.upper_bound(a[i]) != a[i + 1]) {
vector<int> l;
l.push_back(a[j] - *prev(s.find(a[j])));
for (int k = j; k < i; k++) {
l.push_back(a[k + 1] - a[k]);
}
l.push_back(*next(s.find(a[i])) - a[i]);
auto res = NTT::C(vector<int>(begin(l), end(l)), vector<int>(rbegin(l), rend(l)));
for (int k = 1; k <= i - j + 1; k++) {
sum[k] = (sum[k] + res[l.size() - 1 + k]) % kMod;
}
j = i + 1;
}
}
for (int i : a) {
s.erase(i);
}
}
for (int i = 1; i <= n; i++) {
ans = (ans + sum[i] * sum[i] % kMod * i) % kMod, sum[i] = 0;
}
cout << ans << '\n';
}
return 0;
}
I - Left Shifting 2
考虑一个串需要修改的次数。对于一个字符相同的极长连续段,令其长为 \(x\),则需要修改 \(\lfloor\frac{x}{2}\rfloor\) 次。循环位移后最多对一个段产生影响,如果循环点切开一个偶数段,则能使代价减 \(1\),否则不变。所以直接模拟即可。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int t, n, ans, cnt, flag;
string s;
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (cin >> t; t--; ans = flag = 0) {
cin >> s, n = s.size(), cnt = 1;
for (int i = n - 2; ~i && s[i] == s[n - 1]; cnt++, i--);
if (cnt == n) {
cout << n / 2 << '\n';
} else {
for (int i = 0, j = n - cnt, lst = n - 1; i < j; i++) {
if (s[i] == s[lst]) {
cnt++;
} else {
lst = i, ans += cnt / 2, flag |= !(cnt % 2), cnt = 1;
}
}
cout << ans + cnt / 2 - (flag || !(cnt % 2)) << '\n';
}
}
return 0;
}
J - The Quest for El Dorado
使用最短路。距离可以记成一个二元组,分别表示当前用到了哪张车票,当前车票还剩多少里程。当 Dijkstra 转移的时候,如果当前车票不能用,则先不要更新,将这次更新放进这条边的类型所对应的优先队列里,另外维护一个指针表示当前没有用来更新的点的最短距离对应的车票,指针增加时,对指针所指的车票类型的优先队列处理。复杂度 \(O(n\log n)\)。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int kMaxN = 5e5 + 10;
struct node {
int v, c, l;
};
int vis[kMaxN], t, n, m, k;
vector<node> e[kMaxN];
priority_queue<pair<int, int> > q[kMaxN], q2;
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (cin >> t; t--; cout << '\n') {
cin >> n >> m >> k, vis[1] = 1;
for (int u, v, c, l, i = 1; i <= m; i++) {
cin >> u >> v >> c >> l, e[u].push_back({v, c, l}), e[v].push_back({u, c, l});
}
for (node i : e[1]) {
q[i.c].push({-i.l, i.v});
}
for (int a, b; k--;) {
cin >> a >> b;
vector<int> l;
for (; !q[a].empty() && -q[a].top().first <= b; q2.push(q[a].top()), q[a].pop());
for (; !q2.empty() && -q2.top().first <= b;) {
int x = q2.top().second, d = -q2.top().first;
q2.pop();
if (!vis[x]) {
vis[x] = 1, l.push_back(x);
for (node y : e[x]) {
(y.c == a) && (q2.push({-(d + y.l), y.v}), 0);
}
}
}
for (; !q2.empty(); q2.pop());
for (int x : l) {
for (node y : e[x]) {
q[y.c].push({-y.l, y.v});
}
}
}
for (int i = 1; i <= n; i++) {
cout << vis[i], e[i].clear(), vis[i] = 0;
}
for (int i = 1; i <= m; i++) {
for (; !q[i].empty(); q[i].pop());
}
}
return 0;
}
K - Permutation
考虑最朴素的做法,因为这是一个排列,所以可以暴力找每一个数所在的位置。二分的询问次数是 \(n\log n\) 的,很明显无法通过此题。
考虑换一种做法,如果我们同时考虑两个数,把一个数填前一半,另外一个数填后一半,这样我们非常高效的求出了在区间 \([l,r]\) 内存在的数的集合 \(S\),不断递归求解即可。这样的做法非常高效,可以在 \(6666\) 左右次询问内找到答案。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int kMaxN = 1010;
int a[kMaxN], n, X;
vector<int> st;
int C(int l, int r, int x, int y) {
cout << "0 ";
for (int i = 1; i <= (l + r >> 1); i++) {
cout << x << ' ';
}
for (int i = (l + r >> 1) + 1; i <= n; i++) {
cout << y << ' ';
}
return cout << endl, cin >> X, X;
}
void S(int l, int r, vector<int> q) {
if (l == r) {
return a[l] = q[0], void(0);
}
random_shuffle(q.begin(), q.end());
vector<int> Q[2];
int L = 0, x = 0, op;
for (; x < q.size() - 1;) {
op = C(l, r, q[x], q[x + 1]);
if (op == 1) {
x++;
} else {
op = (2 - op) / 2;
for (int i = L; i <= x; i++) {
Q[op].push_back(q[i]);
}
Q[op ^ 1].push_back(q[x + 1]), L = x = x + 2;
}
}
if (L != q.size()) {
op = Q[0].size() == (l + r >> 1) - l + 1;
for (int i = L; i < q.size(); i++) {
Q[op].push_back(q[i]);
}
}
S(l, l + r >> 1, Q[0]), S((l + r >> 1) + 1, r, Q[1]);
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
st.push_back(i);
}
S(1, n, st), cout << "1 ";
for (int i = 1; i <= n; i++) {
cout << a[i] << ' ';
}
return cout << endl, 0;
}
L - Trails
在没有斜边的情况下从 \((0,0)\) 到 \((x,y)\) 的最短距离显然是 \(x+y\)。而每次走斜边则会让距离减 \(1\) 所以我们不妨直接处理会走多少次斜边。
而我们选定的斜线序列一定是由若干纵坐标单调上升的斜线构成。这就转化成了一个简单的 LIS 问题。
而我们求答案的时候只需要扫描线一条一条斜线往里面加就行了。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int kMaxN = 1e6 + 10;
struct node {
int x, y;
} a[kMaxN];
int f[kMaxN], t, n, p, q, tot, top, ans;
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (cin >> t; t--; top = tot = 0) {
cin >> n >> p >> q, ans = (1 + p) * (1 + q) * q / 2 + (1 + q) * (1 + p) * p / 2;
for (int i = 1, x, y; i <= n; i++) {
cin >> x >> y, (x < p && y < q) && (a[++tot] = {x + 1, y + 1}, 0);
}
sort(a + 1, a + 1 + tot, [](node x, node y) { return x.x != y.x ? x.x < y.x : x.y > y.y; }), a[++tot] = {p + 1, 0};
for (int i = 1, lst = 0; i <= tot; i++) {
ans -= lst * (a[i].x - a[i - 1].x);
if (f[top] < a[i].y) {
f[++top] = a[i].y, lst += q - f[top] + 1;
} else {
int w = lower_bound(f + 1, f + 1 + top, a[i].y) - f;
lst += (f[w] - a[i].y), f[w] = a[i].y;
}
}
cout << ans << '\n';
}
return 0;
}
M - Italian Cuisine
对于每个点找到最远的一个点,满足沿这条连线切开,顺时针的那部分不包含圆,可以使用双指针求出,进行一些简单的向量运算和距离运算即可。复杂度 \(O(n)\)。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int kMaxN = 1e5 + 10;
struct node {
int x, y;
} a[kMaxN];
int t, n, xc, yc, r, sum, ans;
node S(node x, node y) { return {x.x - y.x, x.y - y.y}; }
int C(node x, node y) { return x.x * y.y - x.y * y.x; }
int A(node x) { return x.x * x.x + x.y * x.y; }
int B(node s, node t) {
int x = C(S(t, s), S({xc, yc}, s));
return x >= 0 && (__int128)x * x >= (__int128)A(S(t, s)) * r * r;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (cin >> t; t--; sum = ans = 0) {
cin >> n >> xc >> yc >> r;
for (int i = 0; i < n; i++) {
cin >> a[i].x >> a[i].y;
}
reverse(a, a + n);
for (int i = 1, j = 0; i <= n * 2; i++) {
for (sum += C(a[i % n], a[(i - 1) % n]); !B(a[i % n], a[j % n]); sum -= C(a[(j + 1) % n], a[j % n]), j++);
ans = max(ans, sum + C(a[j % n], a[i % n]));
}
cout << ans << '\n';
}
return 0;
}

浙公网安备 33010602011771号