Codeforces Round #746 (Div. 2)

菜的离谱下大分(

Codeforces Round #746 (Div. 2)

\(\mathcal A\)

给定 \(n\) 个数,求用最少的数加到 \(m\),同一个位置的数不能被连续用两次。

找到最大和次大。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
 
int read() {
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}
 
int main() {
	int T = read();
	while(T --) {
		int n = read(), H = read();
		int a = 0, b = 0;
		for(int i = 1; i <= n; i ++) {
			int x = read();
			if(x > a)
				b = a, a = x;
			else if(x > b)
				b = x;
		}
		int ans = H / (a + b) * 2;
		if(H % (a + b)) ans += (H % (a + b) > a) ? 2 : 1;
		printf("%d\n", ans);
	}
	return 0;
}

\(\mathcal B\)

每次只能将相隔至少为 \(k\) 的位置上的数对换,问能否使序列升序排列。

不难发现区间 \([n-k+1,k]\) 是动不了的,看它是否和升序的一样即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
 
const int N = 1e5 + 10;
int n, k, a[N], b[N];
 
int read() {
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}
 
int main() {
	int T = read();
	while(T --) {
		n = read(), k = read();
		for(int i = 1; i <= n; i ++) b[i] = a[i] = read();
		sort(b + 1, b + n + 1);
		bool flag = true;
		for(int i = n - k + 1; i <= k; i ++)
			if(a[i] != b[i]) {flag = false; break;}
		puts(flag ? "YES" : "NO");
	}
	return 0;
}

\(\mathcal C\)

给定一棵有点权的树,问能否将树划分为至少两个,至多 \(k\) 个连通块。

使得所有连通块的异或和相同。

赛时吃了 \(6\) 发罚时才过,菜是原罪(

首先假设能划分,若是偶数个连通块则所有点的异或和一定为 \(0\),反过来,若异或和为 \(0\) 则一定能划分。

否则得到所有的异或和为 \(x\),需要划分为异或和均为 \(x\) 的奇数个连通块。

不难发现一定可以不断合并至三个连通块,所以只要 \(k>2\) 就没有至多多少个的限制了,否则无解。

然后从根往下找,分情况讨论:

  1. 没有子树的异或和为 \(x\),输出 NO
  2. \(1\) 个子树的异或和为 \(x\),将这个子树砍掉,再做一次,若还有异或和为 \(x\) 的子树则有解,否则无解。
  3. \(>1\) 个子树异或和为 \(x\),输出 YES

注意这里统计的实际上是,子树本身异或和为 \(x\),且子树内部没有异或和为 \(x\) 的子子树 的子树的个数。

#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
 
const int N = 2e5 + 10;
int n, k, ans, val, a[N], sum[N];
vector<int> G[N];
 
void Clear() {
	for(int i = 1; i <= n; i ++) G[i].clear();
	val = ans = 0;
}
 
int read() {
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}
 
void dfs1(int u, int fa) {
	sum[u] = a[u];
	for(int v : G[u]) if(v != fa)
		dfs1(v, u), sum[u] ^= sum[v];
}
 
int pos;
 
int dfs2(int u, int fa) {
	int num = 0;
	for(int v : G[u]) if(v != fa) num += dfs2(v, u);
	if(sum[u] == val && !num) {pos = u; return 1;}
	return num;
}
 
int tot[N];
 
int dfs3(int u, int fa) {
	tot[u] = a[u];
	int num = 0;
	for(int v : G[u]) if(v != fa && v != pos) num += dfs3(v, u), tot[u] ^= tot[v];
	return num + (tot[u] == val);
}
 
void Work() {
	n = read(), k = read(); Clear();
	val = 0;
	for(int i = 1; i <= n; i ++) a[i] = read(), val ^= a[i];
	for(int i = 1; i <  n; i ++) {
		int u = read(), v = read();
		G[u].push_back(v);
		G[v].push_back(u);
	}
	if(!val) {puts("YES"); return;}
	if(k == 2) {puts("NO"); return;}
	dfs1(1, 0);
	for(int u : G[1]) ans += dfs2(u, 1);
	if(!ans) puts("NO");
	else if(ans == 1) {
		int now = dfs3(1, 0);
		if(now) puts("YES");
		else puts("NO");
	}
	else puts("YES"); 
}
 
int main() {
	int T = read();
	while(T --) Work();
	return 0;
}

\(\mathcal D\)

交互题,给定一棵树的联通方式,但是不告诉你边权。

每次可以询问一个点集,将回答最大的两点间路径的 \(\gcd\)

要求输出 \((x,y)\),表示两点间路径的 \(\gcd\) 是全局最大的。

只能询问至多 \(12\) 次。

因为 \(\gcd\) 是随着数字增多单调不增的,所以边权最大的边即为最终答案。

考虑用一次询问得到全局最大边权,然后直接对边进行二分。

考虑到边可能不连通,所以需要排个序,按照 dfs 遍历的顺序排序即可。

这是个直接但是可能会假的做法,正解按照欧拉序二分,它有任意一段区间内的点均是联通的特性。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<iostream>
using namespace std;
 
#define X first
#define Y second
#define MP make_pair
typedef pair<int, int> PII;
 
const int N = 1010;
int n, t, val;
bool vis[N]; PII e[N];
vector<int> G[N];
 
int read() {
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}
 
int Ask(vector<int> s) {
	printf("? %d ", s.size());
	for(int i : s) printf("%d ", i);
	fflush(stdout);
	int now; scanf("%d", &now);
	return now;
}
 
void dfs(int u, int fa) {
	if(fa) e[++ t] = MP(fa, u);
	for(int v : G[u]) if(v != fa) dfs(v, u);
}
 
int main() {
	n = read();
	for(int i = 1; i < n; i ++) {
		int u = read(), v = read();
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs(1, 0);
	vector<int> s;
	for(int i = 1; i <= n; i ++) s.push_back(i);
	val = Ask(s);
	int l = 1, r = n - 1;
	while(l < r) {
		int mid = (l + r) >> 1;
		vector<int> p;
		for(int i = l; i <= mid; i ++) {
			if(!vis[e[i].X]) vis[e[i].X] = true, p.push_back(e[i].X);
			if(!vis[e[i].Y]) vis[e[i].Y] = true, p.push_back(e[i].Y);
		}
		for(int i = l; i <= mid; i ++) vis[e[i].X] = vis[e[i].Y] = false;
		int now = Ask(p);
		if(now == val) r = mid; else l = mid + 1;
	}
	printf("! %d %d\n", e[l].X, e[l].Y);
	return 0;
}

\(\mathcal E\)

给定序列,求最长的子序列满足 \(a(l)\&a(l+1)\&\cdots\&a(r)>a(l)\operatorname{xor}a(l+1)\operatorname{xor}\cdots\operatorname{xor}a(r)\)

找性质题。

首先长度为奇数的序列不可能成立,因为 \(\&\) 运算得到有的二进制位 \(\operatorname{xor}\) 都有。

偶数序列,则只需要比 \(\&\) 最高位高的位 \(\operatorname{xor}\) 均为 \(0\) 即可,这个可以 \(O(n)\) 直接做。

对于每一位都这样,总复杂度 \(O(n\log n)\)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
 
const int N = 1e6 + 10;
int n, a[N], pre[N][2];
 
int read() {
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}
 
int main() {
	n = read();
	for(int i = 1; i <= n; i ++) a[i] = read();
	int ans = 0;
	memset(pre, -1, sizeof(pre));
 
	for(int o = 0; o <= 19; o ++) {
		for(int l = 1, r; l <= n; l = r + 1) {
			r = l;
			if(!(a[l] >> o & 1)) continue;
			while(r < n && a[r + 1] >> o & 1) r ++;
			
			int s = 0;
			pre[0][(l - 1) & 1] = l - 1;
			for(int i = l; i <= r; i ++) {
				int k = a[i] >> (o + 1);
				s ^= k;
				if(pre[s][i & 1] == -1)
					pre[s][i & 1] = i;
				else
					ans = max(ans, i - pre[s][i & 1]);
			}
			
			s = 0;
			pre[0][(l - 1) & 1] = -1;
			for(int i = l; i <= r; i ++) {
				int k = a[i] >> (o + 1);
				s ^= k;
				pre[s][i & 1] = -1;
			}
		}
	}
	printf("%d\n", ans);
	return 0;
}

\(\mathcal F1\)

给定 \(0/1\) 网格,有四种翻转操作,求将网格翻转至全 \(0\) 的最小代价:

  1. 翻转包含 \((1,1)\) 的子矩阵,代价 \(1\)
  2. 翻转包含 \((n,1)\) 的子矩阵,代价 \(2\)
  3. 翻转包含 \((1,m)\) 的子矩阵,代价 \(4\)
  4. 翻转包含 \((n,m)\) 的子矩阵,代价 \(3\)

显然 \(2,3\) 操作是 useless 的。

一个绝妙的转换,用 \(a(i,j)\) 表示 \((i,j)+(i+1,j)+(i,j+1)+(i+1,j+1)\) 的奇偶。

那么原图全 \(0\) 的充要条件是 \(a(i,j)\)\(0\)

每次操作 \(1\) 只会改变 \(a(i,j)\),而操作 \(4\) 却会改变 \(a(i-1,j-1),a(n,j-1),a(i-1,m),a(n,m)\)

显然若四者均为 \(1\) 则省了一个代价,否则无用,当然有用也只能用一次。

因为再次使用操作 \(4\) 则可以用至多 \(6\) 次操作 \(1\) 来替换,因为 \(a(n,m)\) 翻了两次相当于没翻。

故只需要找是否有进行一次操作 \(4\) 的机会即可,时间复杂度 \(O(nm)\)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
 
const int N = 510;
int n, m, a[N][N];
char s[N][N];
 
int read() {
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}
 
int main() {
	n = read(), m = read();
	for(int i = 1; i <= n; i ++) scanf("%s", s[i] + 1);
	int ans = 0;
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= m; j ++) {
			a[i][j] += (s[i][j] == 'B');
			a[i][j] += (s[i][j + 1] == 'B');
			a[i][j] += (s[i + 1][j] == 'B');
			a[i][j] += (s[i + 1][j + 1] == 'B');
			a[i][j] &= 1;
			ans += a[i][j];
		}
	bool flag = false;
	if(a[n][m])
		for(int i = 2; i <= n && !flag; i ++) for(int j = 2; j <= m; j ++)
			if(a[i - 1][j - 1] && a[n][j - 1] && a[i - 1][m]) {flag = true; break;}
	printf("%d\n", ans - flag);
} 

\(\mathcal F2\)

同上,只是代价变为 \(1\ 3\ 4\ 2\)

显然 \(2,3\) 还是没用。

但这次只要 \(a(i-1,j-1),a(n,j-1),a(i-1,m)\) 三者均有数就能进行 \(4\) 操作。

分析得到相同的 \(i/j\) 不会被操作 \(4\) 进行两次或以上,那样可以被至多 \(4\) 次操作 \(1\) 替换。

故找到所有 \(a(i-1,j-1),a(n,j-1),a(i-1,m)\) 均为 \(1\)\((i,j)\),做二分图最大匹配即可。

真是绝世好题 QwQ

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
 
const int N = 510;
int n, m, a[N][N], mat[N];
bool vis[N];
char s[N][N];
vector<int> G[N];
 
int read() {
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}
 
bool dfs(int u) {
	for(int v : G[u]) if(!vis[v]) {
		vis[v] = true;
		if(! mat[v] || dfs(mat[v])) {
			mat[v] = u;
			return true;
		}
	}
	return false;
}
 
int main() {
	n = read(), m = read();
	for(int i = 1; i <= n; i ++) scanf("%s", s[i] + 1);
	int ans = 0;
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= m; j ++) {
			a[i][j] += (s[i][j] == 'B');
			a[i][j] += (s[i][j + 1] == 'B');
			a[i][j] += (s[i + 1][j] == 'B');
			a[i][j] += (s[i + 1][j + 1] == 'B');
			a[i][j] &= 1;
			if(i != n || j != m) ans += a[i][j];
		}
	for(int i = 2; i <= n; i ++)
		for(int j = 2; j <= m; j ++)
			if(a[i - 1][j - 1] && a[n][j - 1] && a[i - 1][m]) G[i].push_back(j);
	int num = 0;
	for(int i = 2; i <= n; i ++) {
		memset(vis, false, sizeof(vis));
		num += dfs(i);
	}
	printf("%d\n", ans - num + (a[n][m] ^ (num & 1)));
	return 0;
}
posted @ 2021-10-05 23:06  LPF'sBlog  阅读(53)  评论(0编辑  收藏  举报