[ABC347] AtCoder Beginner Contest 347 题解
[ABC347] AtCoder Beginner Contest 347 题解
A
模拟。
B
SA 模板,把所有子串丢进哈希表里即可。
C
逆天题,这个分讨并不显然。
考虑计算所有天数到今天的偏移量,然后如果最远的和最近的天数的距离 \(\le A\) 肯定可以,否则可以把所有天向右平移一段距离,然后使得最远的天到达第二周的休息日。
for(int i = 1; i <= n; i ++) {
int d; cin >> d;
c[i] = (d - 1) % (a + b) + 1;
mn = min(mn, (d - 1) % (a + b) + 1);
mx = max(mx, (d - 1) % (a + b) + 1);
}
sort(c + 1, c + n + 1);
if(mx - mn + 1 <= a) cout << "Yes";
else {
int pos = 0;
for(int i = 1; i <= n; i ++) {
c[i] -= mn;
c[i] ++;
}
for(int i = 1; i <= n; i ++)
if(c[i] <= a && c[i + 1] > a) {
pos = i;
break;
}
int len = a - c[pos];
if(c[pos + 1] + len > a + b && c[n] + len - a - b <= a) cout << "Yes";
else cout << "No";
}
D
鉴定为比 C 简单。
考虑按位处理,如果某一位上 \(C\) 是 1,那么将剩余 \(\text{popcount}\) 更多的那个数在这位上设为 1,这样一轮枚举下来,可能会若干 \(\text{popcount}\),如果总数是奇数肯定不行,否则在每一个 \(C\) 是 0 的位置同时放两个 1,如果有剩余无解。
需要特判 0 0 0
。
if(n == 0 && a == 0 && b == 0) {
cout << 0 << ' ' << 0 << '\n';
return 0;
}
c = __builtin_popcount(n);
if(a + b < c) return cout << -1 << '\n', 0;
for(int i = 0; i < 60; i ++) {
if(n >> i & 1) {
if(a > b) a --, x += (1ll << i);
else b --, y += (1ll << i);
}
}
if(a ^ b) return cout << -1 << '\n', 0;
for(int i = 0; i < 60; i ++) {
if(~n >> i & 1) {
x += (1ll << i);
y += (1ll << i);
a --;
}
if(!a) break;
}
if(a) return cout << -1 << '\n', 0;
cout << x << ' ' << y << '\n';
E
如果把每个颜色表示成若干个竖直的线段(类似时间分治),那么每个位置上能得到的权值就是当时的集合大小,这个可以简单模拟计算出来,那么每个颜色的答案就是所有线段上点的和,前缀和即可。
for(int i = 1; i <= q; i ++) {
cin >> x[i];
if(!s.count(x[i])) s.insert(x[i]);
else s.erase(x[i]);
a[i] = s.size();
}
for(int i = 1; i <= q; i ++) a[i] += a[i - 1];
for(int i = 1; i <= q; i ++) {
if(!ok[x[i]]) {
ok[x[i]] = 1;
b[x[i]] = i;
}
else {
ans[x[i]] += a[i - 1] - a[b[x[i]] - 1];
ok[x[i]] = 0;
}
}
for(int i = 1; i <= n; i ++) if(ok[i]) ans[i] += a[q] - (b[i] ? a[b[i] - 1] : 0);
F
考虑分成 6 种情况讨论。
这六种情况覆盖了所有三个正方形之间的关系,然后需要做的就是每种情况分讨,然后二位前缀和随便搞一下就好了。
// a, b, c, d 表示左上、右上、左下、右下的矩形最大值
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
g[i][j] += (g[i - 1][j] + g[i][j - 1] - g[i - 1][j - 1]);
n = n - k + 1, m = m - k + 1;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
s[i][j] = (g[i + k - 1][j + k - 1] - g[i - 1][j + k - 1] - g[i + k - 1][j - 1] + g[i - 1][j - 1]);
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
a[i][j] = max(max(s[i][j], a[i - 1][j]), a[i][j - 1]);
for(int i = 1; i <= n; i ++)
for(int j = m; j; j --)
b[i][j] = max(max(s[i][j], b[i - 1][j]), b[i][j + 1]);
for(int i = n; i; i --)
for(int j = 1; j <= m; j ++)
c[i][j] = max(max(s[i][j], c[i + 1][j]), c[i][j - 1]);
for(int i = n; i; i --)
for(int j = m; j; j --)
d[i][j] = max(max(s[i][j], d[i + 1][j]), d[i][j + 1]);
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++) {
cm[j] = max(cm[j], s[i][j]);
rm[i] = max(rm[i], s[i][j]);
}
for(int i = k; i <= m; i ++)
ans = max(ans, cm[i] + a[n][i - k] + b[n][i + k]);
for(int i = k; i <= n; i ++)
ans = max(ans, rm[i] + b[i - k][1] + d[i + k][1]);
for(int i = k; i <= n; i ++) {
for(int j = k; j <= m; j ++) {
ans = max(ans, b[n][j] + c[i][j - k] + a[i - k][j - k]);
ans = max(ans, b[i - k + 1][1] + d[i + 1][j] + c[i + 1][j - k]);
ans = max(ans, a[n][j - k + 1] + d[i][j + 1] + b[i - k][j + 1]);
ans = max(ans, d[i][1] + b[i - k][j] + a[i - k][j - k]);
}
}
G
摸了,学完网络流再补。
总结
C 罚时吃饱了,所以以后交之前应该思考一些corner case,WA了之后要仔细想一下哪里漏了。
F 没切出来,主要是没见过这种套路,这次记住就好了,对于这种情况少的可以考虑元素之间的关系。
本场亮点在于跳 C 切 D。