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\n", ans);
    for (int i = 1; i < n; ++i) {
        ans += res[i];
        printf("%lld\n", 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;
}

原文地址:https://www.cnblogs.com/Vexoben/p/11730087.html

时间: 2024-10-12 22:15:30

AGC006做题小记的相关文章

AGC005做题小记

比赛链接:AGC005 C. Tree Restoring 题意 给出树上每个节点到其它节点的最远距离,问是否可能. $ n ≤ 100 $ . 题解 离一个点最远的节点是直径两端点之一. 先把直径上的点切出来.剩余的点判断一下是不是在 $ \lceil \frac{d}{2} \rceil +1 $ 和 $ d + 1 $ 之间即可. My Submission D. ~K Perm Counting 题意 计数 $ n $ 个数全排列个数,使得 $ abs(a_i - i) ≠ K $ $

Codeforces VP/补题小记 (持续填坑)

Codeforces VP/补题小记 1149 C. Tree Generator 给你一棵树的括号序列,每次交换两个括号,维护每次交换之后的直径. ? 考虑括号序列维护树的路径信息和,是将左括号看做 \(-1\) ,右括号看做 \(1\) ,那么一段竖直向上的路径可以表示为括号序列的一个区间和,一段竖直向下的路径可以看做括号序列的一个区间和的相反数.我们要维护的是树的直径,也就是一段连续的和减去紧随其后的一段连续的差.具体来说就是 \[ \max_{\forall [l,r]}\{\sum_{

python做题

Python题目 1.打印一个九九乘法表 #!/usr/bin/env python # -*- coding: utf-8 -*- """ __author__ = 'YeXiaodong' __QQ__= '12519460' __Email__ = '[email protected]' """ x = 1 list_table = [] print('九九乘法表'.center(100,' ')) while x < 10: lis

做题神器风靡:在线教育虚火旺盛的罪魁祸首是谁?

在线教育的巨大潜力有目共睹,不管是创业者还是巨头都纷纷杀入其中.在线教育本身囊括的范围极其广阔,从胎教.学龄前教育.中小学教育.高等教育,再到职业教育,乃至细分化的英语教育.技能教育等,构成一个完整的教育生态圈.但让人无奈的是,国内在线教育最火爆的却还是中小学教育. 而且由于国内教育体系本身存在的弊端,导致中小学教育主要以书山题海为工具,向高分发起追逐.在这样的大背景下,在线教育最火爆的是各种做题神器App.做题神器的风靡,让整个在线教育行业看起来形势一片大好.然而,做题神器真的就是解决在线教育

USACO 做题小结

还记得之前,发过一篇阶段性总结与未来规划..结果由于最近rp爆发(保研成功+进wf)后者显然靠bin神,前者也是运气.因此,放松了一段时间.然后就开始刷usaco了,原因是不用花时间找解题报告在NOCOW上全部都有,很是方便.所以只需单独开一片随笔把每天做题总结一下. Chapter1-Getting started(入门) 都是超级大水题就略过了. Chapter2-Bigger Challenges(更大的挑战) 2.1 castle  这是一道基本的搜索题目,很基础.前面两个值直接搜的,后

acm做题心得&amp;&amp;语录

1.切一道难题,总比做百道水题轻松,而且显然前者对你有本质的提升.(后续更新) acm做题心得&&语录,布布扣,bubuko.com

我做题、比赛的头文件,不断更新中

我做题.比赛的头文件~ 特别是那个rep特别好用,写起来明显速度快多了 #include <cstdio> #include <iostream> s#include <string.h> #include <cstdlib> #include <algorithm> #include <queue> #include <vector> #include <cmath> #include <map>

长对话做题规律

一.规律 1.题目与答案来源按照顺序分配,偶尔发生邻近错位: 2.对于大多数题目,都可以按照听啥选啥的原则,当所有选项都听到了,就按照3的方法: 3.做题三大规律:1)重复即所得 2)最后即所得 3)所听即所得 ,注意先后顺序 4.必须通过ABCD预判问题 5.注意个题目之间的逻辑和前后的一致性. 二.策略 1.历年真题反复听,注意对标志词的关注. 2.多记四级单词.

大一下学期以来做题总结、

这段时间以来.除了搜索专题和KMP专题 其他专题都不怎么愿意想了. 就比如今天的水题. 代码敲错. 输出格式没看清题意. 看题又不仔细. 等交上去错一发之后在回过头来看题目. 而且看题这方面真的好差. 经常性题目没读完就开始去敲代码.在敲代码过程中再去看题意. 希望自己读题的时候要认真.争取把题目读准.读快. 还有一个坏习惯.做题的时候喜欢听歌.这样在做思维题的时候多少会有点影响把. 这样好了,以后在做题时间一定一定不要听歌. 还有就是思维方面比以前欠缺一点了. 可能是因为没有比赛时的感觉的了.