比赛链接: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