zoukankan      html  css  js  c++  java
  • AGC006做题小记

    比赛链接:AGC006

    C.Rabbit Exercise

    题意

    有 n 只兔子,一开始第 i 只兔子位于 x[i] 。有 m 次操作,第 i 次操作给定 a[i](1 < a[i] < n), 表示让第 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;
    }
    
  • 相关阅读:
    easy ui 表单ajax和from两种提交数据方法
    easy ui 下拉级联效果 ,下拉框绑定数据select控件
    easy ui 下拉框绑定数据select控件
    easy ui 异步上传文件,跨域
    easy ui 菜单和按钮(Menu and Button)
    HTTP 错误 404.3
    EXTJS4.2 后台管理菜单栏
    HTML 背景图片自适应
    easy ui 表单元素input控件后面加说明(红色)
    EXTJS 4.2 添加滚动条
  • 原文地址:https://www.cnblogs.com/Vexoben/p/11730087.html
Copyright © 2011-2022 走看看