[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 种情况讨论。

img

这六种情况覆盖了所有三个正方形之间的关系,然后需要做的就是每种情况分讨,然后二位前缀和随便搞一下就好了。

// 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。

posted @ 2024-03-31 21:25  MoyouSayuki  阅读(93)  评论(0编辑  收藏  举报
:name :name