VEXOBEN
Vexoben
Oct 7, 2018
It takes 16 minutes to read this article.

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