AGC006做题小记

Posted vexoben

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AGC006做题小记相关的知识,希望对你有一定的参考价值。

比赛链接:AGC006

C.Rabbit Exercise

题意

有 n 只兔子,一开始第 i 只兔子位于 x[i] 。有 m 次操作,第 i 次操作给定 ai, 表示让第 a[i] 只兔子等概率选择 a[i] - 1 或 a[i] + 1 中的一只兔子,跳到它相对于那只兔子的对称点。

要求你输出重复这 m 个操作 k 次之后, 每只兔子坐标的期望。

3 ≤ N, M ≤ 100000

1 ≤ K ≤ 1018

题解

800分比1500分难系列

x 关于 y 对称做一次跳跃会跳到 2y - x

那么兔子 a 关于兔子 b 跳跃的时候, b 对于他跳到的位置的贡献是线性的。结合期望的线性性,我们可以得出让 t 进行一次跳跃就是将 x[t] 变为 0.5 * (2 * x[t - 1] - x[t]) + 0.5 * (2 * x[t + 1] - x[t]) = x[t - 1] + x[t + 1] - x[t]

似乎很难处理。这时如果画下图,会发现跳跃时 x 数组的差分变化很有意思。

令 d1 = x[t] - x[t - 1], d2 = x[t + 1] - x[t]

向 t - 1 跳的结果是, d1 = -d1, d2 = 2 * d1 + d2

向 t + 1 跳的结果是, d2 = -d2, d1 = d1 + 2 * d2

再由期望的线性性,我们发现让 t 进行一次跳跃就是交换了 d1 和 d2

从而, m 次操作等同于作用在差分数组上的置换

模拟前 m 次操作就可以求出这个置换。然后将置换拆为若干轮换,就可以求出它的 k 次幂。

x1 是不会变的,得到差分数组就可以还原出最终序列了。

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 1e5 + 10;
 
int n, m, a[N], p[N], vis[N];
vector<int> vec;
LL x[N], k, d[N], res[N];
 
void dfs(int x) {
    vis[x] = 1;
    vec.push_back(x);
    if (!vis[p[x]]) dfs(p[x]);
}
 
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) {
        scanf("%lld", &x[i]);
    }
    for (int i = 1; i < n; ++i)
        d[i] = x[i + 1] - x[i];
    scanf("%d%lld", &m, &k);
    for (int i = 1; i < n; ++i)
        p[i] = i;
    for (int i = 1; i <= m; ++i) {
        scanf("%d", &a[i]);
        swap(p[a[i] - 1], p[a[i]]);
    }
    for (int i = 1; i < n; ++i) {
        if (vis[i]) continue;
        vec.clear();
        dfs(i);
        int siz = vec.size();
        int del = k % siz;
        for (int j = 0; j < vec.size(); ++j) 
            res[vec[j]] = d[vec[(j + del) % siz]];
    }
    LL ans = x[1];
    printf("%lld
", ans);
    for (int i = 1; i < n; ++i) {
        ans += res[i];
        printf("%lld
", ans);
    }
    return 0;
}

D.Median Pyramid Hard

题意

给定长度为 2n - 1 的排列a, 作为金字塔的第 1 层然后我们构造一个 n 层的金子塔,第 i 层有 2 * (n - i) + 1 个数。从第二层开始,第 x 个数都等于上一层第 x, x + 1, x + 2个数的中位数,问最上面的数是多少。

n ≤ 100000

题解

1300分比1500分难系列

显然有一个二分答案,然后将原序列变为01序列。

check的时候,如果底层01交错,最上面的数就等于第一个数;否则,考虑如果有连续两个相同的0,一定会导致它上方形成一个两个0的“柱子”,这个柱子一开始向上,在碰到边界后就向左上或者右上。如果有多个柱子,可以发现一定是向上的距离长的那个取胜。所以只需要找到最靠近 n 的那根柱子即可。

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 1e5 + 10;
 
int n, m, a[N * 2], b[N * 2];
 
int check(int mid) {
    int dis = 0x3f3f3f3f;
    for (int i = 1; i <= n * 2 - 1; ++i)
        b[i] = (a[i] >= mid);
    int ans = b[1];
    for (int i = 1; i < n * 2 - 1; ++i) {
        if (b[i] == b[i + 1]) {
            int d = min(abs(n - i), abs(n - i - 1));
            if (d < dis) {
                dis = d;
                ans = b[i];
            }
        }
    }
    return ans;
}
 
int main() {
    cin >> n;
    for (int i = 1; i <= n * 2 - 1; ++i)
        scanf("%d", &a[i]);
    int l = 1, r = 2 * n - 1;
    while (l < r) {
        int mid = (l + r + 1) >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    cout << l << endl;
    return 0;
}

E.Rotate 3x3

题意

给定一个 3 行 n 列的矩形,第 i 行 j 列上的数是 i + 3j - 3。每次可以选择一个 3 × 3 的矩形,将它旋转 180°, 问是否能将矩形操作到一个给定的目标状态。

题解

手玩大法好!

首先你要写个程序帮你旋转矩形。手玩十分钟后就能发现几个非常有用的性质。

首先列与列之间的数不能交换,一列中模 3 余 2 的数总是在中间。

其次,如果两列距离为偶数,可以让这两列同时取反(交换第一行和第三行的元素)

最后,如果两列距离为 2, 可以交换这两列,并且取反中间那列

将第 i 列的值记为它最终到的列,奇列逆序对奇偶性等于偶列反列数奇偶性为可行,偶列亦然。

这样会WA3个点,原因是还要判有没有原矩形中的奇列跑到偶列去了。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;

int n, a[3][N];
int p[N], v[N], to[N];
int b[N], tr[N * 3];

void Add(int x) {
    while (x <= n * 3) {
        tr[x] ^= 1;
        x += x & -x;
    }
}

int Query(int x) {
    int ans = 0;
    while (x) {
        ans ^= tr[x];
        x -= x & -x;
    }
    return ans;
}

int inv(int *a, int n) {
    memset(tr, 0, sizeof tr);
    int ans = 0;
    for (int i = 1; i <= n; ++i) {
        ans ^= Query(n * 3) ^ Query(a[i]);
        Add(a[i]);
    }
    return ans;
}

int main() {
    cin >> n;
    for (int i = 0; i < 3; ++i) {
        for (int j = 1; j <= n; ++j)
            scanf("%d", &a[i][j]);
    }
    for (int i = 1; i <= n; ++i) {
        if (a[0][i] % 3 == 1) {
            if (a[1][i] != a[0][i] + 1) return 0 * puts("No");
            if (a[2][i] != a[1][i] + 1) return 0 * puts("No");
            p[i] = (a[0][i] - 1) / 3 + 1;
            v[i] = 0;
        }
        if (a[0][i] % 3 == 0) {
            if (a[1][i] != a[0][i] - 1) return 0 * puts("No");
            if (a[2][i] != a[1][i] - 1) return 0 * puts("No");
            p[i] = a[0][i] / 3;
            v[i] = 1;
        }
        if (a[0][i] % 3 == 2) return 0 * puts("No");
    }
    for (int i = 1; i <= n; ++i)
        to[p[i]] = i;
    for (int i = 1; i <= n; ++i) {
        if ((i + to[i]) & 1) return 0 * puts("No");
    }
    
    int m = (n + 1) / 2;
    for (int i = 1; i <= m; ++i)
        b[i] = p[i * 2 - 1];
    int tmp = inv(b, m);
    for (int i = 1; i <= n; ++i)
        if (i % 2 == 0)
            tmp ^= v[i];
    if (tmp) return 0 * puts("No");

    m = n / 2;
    for (int i = 1; i <= m; ++i)
        b[i] = p[i * 2];
    tmp = inv(b, m);
    for (int i = 1; i <= n; ++i)
        if (i & 1)
            tmp ^= v[i];
    if (tmp) return 0 * puts("No");
    return 0 * puts("Yes");
}

F.Blackout

题意

给定一张有向图,如果 x 到 y 有边, y 到 z 有边,那么可以给 z 到 x 也连上边

初始时只有 m 条边,要求尽可能多地加边后图的边数。

题解

1700分比1500分难系列(雾)

这道题还是很巧妙的。

首先我们可以把弱联通块分开计算。

弱联通块就是将所有边替换为无向边后图联通。

然后我们对联通块三染色。

如果染色不需要三色,显然不能再加边;

如果恰好可以用三色染色,设划分出三个集合大小分别为A, B, C,那么对答案的贡献就是A * B + B * C + C * A.这是因为是弱联通,且同一联通块间无边,不同集合中的两点必然被两条边连接起来,从而一定有第三条边。

如果三色无法染色,就是说存在同一集合中的边,那么加完边后一定是完全图。证明和前者类似。

需要注意一下弱联通的写法。

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 2e5 + 10;

int n, m, E, cnt;
int fir[N], nex[N], arr[N], val[N], vis[N];
LL A, B, C, flag, col[N];

inline void Add_Edge(int x, int y, int w) {
    nex[++E] = fir[x];
    fir[x] = E; arr[E] = y; val[E] = w;
}

void dfs(int x) {
    vis[x] = 1;
    if (col[x] == 0) ++A;
    if (col[x] == 1) ++B;
    if (col[x] == 2) ++C;
    for (int i = fir[x]; i; i = nex[i]) {
        if (val[i] == 1) ++cnt;
        if (vis[arr[i]]) {
            if (col[arr[i]] != (col[x] + val[i]) % 3)
                flag = 1;
        }
        else {
            col[arr[i]] = (col[x] + val[i]) % 3;
            dfs(arr[i]);
        }
    }
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; ++i) {
        int x, y;
        scanf("%d%d", &x, &y);
        Add_Edge(x, y, 1);
        Add_Edge(y, x, 2);
    }
    memset(col, 0xff, sizeof col);
    LL ans = 0;
    for (int i = 1; i <= n; ++i) {
        if (vis[i]) continue;
        A = B = C = cnt = flag = 0;
        col[i] = 0;
        dfs(i);
        if (flag) ans += (A + B + C) * (A + B + C);
        else if (!A || !B || !C) ans += cnt;
        else ans += (A * B) + (B * C) + (A * C);
    }
    cout << ans << endl;
    return 0;
}

以上是关于AGC006做题小记的主要内容,如果未能解决你的问题,请参考以下文章

做题agc006C - Rabbit Exercise——模型转换

[ AGC006 F ] Blackout

Atcoder agc006D

AT2164 AGC006C Rabbit Exercise

AGC006C & CF1110E

[agc006f]blackout