Codeforces Round #812 (Div. 2) 题解

https://codeforces.com/contest/1713

估计也就我赛中才D都写不出来了

A题

题意:
给定二维平面上\(n\)个点的坐标,保证每个点坐标\(x = 0或者y = 0\),问你从\((0, 0)\)点出发,最少走多少距离可以经过所有点并且回到起点。

思路:
因为每个点坐标\(x = 0或者y = 0\),这样子显然我们在每条轴上走到离起点最远的点再回去就行,并且可以看出来这样走也是最小距离。

void solve() {
	int n;
	cin >> n;
	int maxx, minx, maxy, miny;
	maxx = maxy = 0;
	minx = miny = 0;
	for(int i = 1 ; i <= n ; i ++) {
		int x, y;
		cin >> x >> y;
		maxx = max(maxx, x);
		maxy = max(maxy, y);
		minx = min(minx, x);
		miny = min(miny, y);
	}
	cout << (maxx + maxy - minx - miny) * 2 << "\n";
}

B题

题意:
给你一个数组,每次可以选择一个区间,区间内的数都减\(1\)
问你当前数组是否是操作最少次数就能使得整个区间为\(0\)的数组的一种排列。

思路:
很容易发现,要使得操作次数最小,整个数组必须是一个山峰的形状(先非严格单调递增再非严格单调递减),否则就把它变成一个山峰形状操作次数会更少。

int a[N];
int n;
 
void solve() {
	cin >> n;
	int maxd = 0, pos = 0;
	for(int i = 1 ; i <= n ; i ++) {
		cin >> a[i];
		if(maxd < a[i]) {
			maxd = a[i];
			pos = i;
		}
	}
	bool flag = true;
	for(int i = 1 ; i < pos ; i ++)
		if(a[i + 1] < a[i]) flag = false;
	for(int i = pos ; i < n ; i ++)
		if(a[i] < a[i + 1]) flag = false;
	if(flag) cout << "YES\n";
	else cout << "NO\n";
}

C题

题意:
要求构造一个\(0~n-1\)的排列\(p\),使得每个位置上的数\(i + p_i\)是完全平方数.

思路:直接贪心,从\(n - 1 ~ 0\)位置上,然后从大往小放能放的第一个数。

bool st[N];
int ans[N];
int n;
 
void solve() {
	cin >> n;
	memset(st, false, sizeof(bool) * (n + 5));
	
	int lim = sqrt(2 * n - 2);
	for(int i = n - 1 ; i >= 0 ; i --) {
		int s = sqrt(i);
		if(s * s != i) s ++;
		for(int j = lim ; j >= s ; j --) {
			if(j * j - i > n - 1) continue;
			if(!st[j * j - i]) {
				ans[i] = j * j - i;
				st[j * j - i] = true;
				break;
			}
		}
 	}
	for(int i = 0 ; i < n ; i ++) cout << ans[i] << " ";
	cout << "\n";
}

D题

题意:
交互题,给出一场有\(2^n\)个人参加淘汰制的锦标赛,每次可以询问两个人谁胜场多,要求至多询问次\(upper{2/3 * 2^n}\), 找到最终的胜者。

思路:
我们每次把四个人一组进行查询,需要多少次可以查出来四个人力谁是胜者?答案是2次。
假设当前四个人比之前,他们都获胜了\(t\)
那么比完的结果必然是\(t, t, t+1, t+2\)的一个排列
设四个人分别是 \(x,y,z,k\)
我们先询问\(x, z\)
\(0\),那么我们再询问\(y, k\)就能得到当前四人的胜者
\(1\),那么我们再询问\(x, k\)就能得到当前四人的胜者
\(2\),那么我们再询问\(y, z\)就能得到当前四人的胜者
最后注意剩下两个人的情况即可。

int n;
 
int query(int x, int y){
	cout << "? " << x << " " << y << endl;
	int res;
	cin >> res;
	return res;
}
 
void solve() {
	vector<int> v;
	vector<int> temp;
	cin >> n;
	n = 1 << n;
	for(int i = 1 ; i <= n ; i ++) v.push_back(i);
	while(v.size() > 2) {
		temp.clear();
		for(int i = 0 ; i < v.size() ; i += 4){
			int x = v[i], y = v[i + 1], z = v[i + 2], k = v[i + 3];
			int t = query(x, z);
			if(t == 0) {
				t = query(y, k);
				if(t == 1) temp.push_back(y);
				else temp.push_back(k);
			}
			else if(t == 1) {
				t = query(x, k);
				if(t == 1) temp.push_back(x);
				else temp.push_back(k);
			} else {
				t = query(y, z);
				if(t == 1) temp.push_back(y);
				else temp.push_back(z);	
			}
		}
		v = temp;
	}
	if(v.size() == 1) cout << "! " << v[0] << endl;
	else {
		int t = query(v[0], v[1]);
		if(t == 1) cout << "! " << v[0] << endl;
		else cout << "! " << v[1] << endl;
	}
}

E题

题意:
给出一个矩阵,每次可以选择一个\(k\),然后交换所有\(a[k][i]\)\(a[i][k]\),问能得到的最小字典序是多少。

思路:
首先肯定越前面的数越重要,因此我们只需要按顺序判断当前位置\((a[i][j]和a[j][i])\)是否需要交换。
因为每个位置的交换可以选择\(k = i或者k=j\)
因此每个要交换的位置,\(k\)只能二选一,而每个不需要的位置,可以两个位置都不交换或者两个位置都交换。
用扩展域并查集,详见代码。

int p[N * 2];
int n;
int a[N][N];

int find(int x){
	if(x != p[x])
		p[x] = find(p[x]);
	return p[x];
}

bool same(int x, int y) {
	x = find(x), y = find(y);
	return x == y;
}

void merge(int x, int y) {	// 让 x 连到 y 上 
	int fx = find(x), fy = find(y);
	p[fx] = fy;
}

void solve() {
	cin >> n;
	for(int i = 1 ; i <= n * 2 ; i ++) p[i] = i;
	for(int i = 1 ; i <= n ; i ++)
		for(int j = 1 ; j <= n ; j ++)
			cin >> a[i][j];
	
	for(int i = 1 ; i <= n ; i ++)
		for(int j = i + 1 ; j <= n ; j ++) {
			if(a[i][j] > a[j][i]) {	// i 和 j 有一个要交换 	交换i或者交换j(敌人) 
				if(same(i, j)) continue; // 如果 i 和 j 是朋友 
				merge(i, j + n);	// i 连到 j + n 上 				find(i) > n 
				merge(i + n, j);	// i + n 连到 j 上 				find(j) <= n
			}
			else if(a[i][j] < a[j][i]) {	// i 和 j 都不要交换 或者 同时交换 (朋友) 
				if(same(i, j + n)) continue;	// 如果 i 和 j 是敌人 
				merge(i, j);				// i 连到 j 上 			find(i) = find(j)
				merge(i + n, j + n);		// i + n 连到 j + n 上 
			}
		}
	
	for(int k = 1 ; k <= n ; k ++) {	// 从敌对关系中任选一种交换  find(i) > n  or  find(j) <= n
		if(find(k) <= n) continue;	// 选择 find(i) > n 的交换, find(i) <= n 的不交换 
//		if(find(k) > n) continue;
		for(int i = 1 ; i <= n ; i ++)
			swap(a[k][i], a[i][k]);
	} 
	
	for(int i = 1 ; i <= n ; i ++) {
		for(int j = 1 ; j <= n ; j ++)
			cout << a[i][j] << " ";
		cout << "\n";
	}
		
}
posted @ 2022-08-07 15:29  beatlesss  阅读(248)  评论(0编辑  收藏  举报