Codeforces Round #737 (Div. 2)

Codeforces Round #737 (Div. 2)

褒贬不一,个人认为题目质量可观,赛时只会 A~C 然后掉大分 😞

A

将一个没有重复数字的数列分为恰好两个非空数列,求品均值之和的最大值。

不难证明最大的数单独一列是最优的。

B

判断是否可以将一个没有重复数字的数列分为恰好 \(K\) 个非空数列,再合并,使得它升序排列。

一开始读错题了,以为可以拆无限次,然后 \(K\geq 3\)​ 直接 puts("Yes"),WA 了两发 😢

有一个比较巧妙的做法,考虑将一个数字映射为它最终需要到的位置。

那么只有一个连续段是可以分到一起的,看最短段数是否 \(\leq K\)​ 即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 1e5 + 10;
int T, n, m, 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;
}

bool cmp(int x, int y){return a[x] < a[y];}

int main(){
	T = read();
	while(T --){
		n = read(), m = read();
		for(int i = 1; i <= n; i ++) a[i] = read(), b[i] = i;
		sort(b + 1, b + n + 1, cmp);
		int t = 0;
		for(int i = 1; i <= n; i ++)
			if(i == 1 || b[i] != b[i - 1] + 1) t ++;
		if(t > m) puts("No");
		else puts("Yes");
	}
	return 0;
}

C

给定 \(n\)\(k\) 为二进制数,可以随便填 \(0/1\),求使 \(a_1\&a_2\&\dots\&a_n\geq a_1\ {\rm xor}\ a_2\ {\rm xor}\dots\ {\rm xor}\ a_n\) 的方案数。

分 严格大于 和 恰好等于 两种情况统计比较简单。

不难发现严格大于的情况只出现在 \(n\) 为偶数的时候,并且某一位上全 \(1\),前面位恰好等于,后面位随意。

而恰好相等就是要么某一位都为 \(0/1\),那么首先预处理推得:

\(M_1\):表示使 \(n\) 个位异或为 \(0\) 的方案数 \(=\binom{n}{0}+\binom{n}{2}+\cdots\),即偶数个 \(1\)

那么不难发现使 \(n\)\(i\) 位二进制串恰好相等的方案数为:

  1. \(n\) 为奇数:\((M_1+1)^i\)。加上全 \(1\) 的情况。
  2. \(n\) 为偶数:\((M_1-1)^i\)。减去全 \(1\) 的情况。

\(f(i)\)​ 表示这个值,那么对于严格大于的情况(仅当 \(n\) 为偶数时考虑):

\[S=\sum\limits_{i=1}^k f(i-1)\times (2^{k-i})^n \]

最终 \(Ans=S+f(k)\),时间复杂度 \(O(n+k\log n)\)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;
const int N = 2e5 + 10;
const LL MOD = 1e9 + 7;
int T, n, k;
LL fac[N], inv[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;
}

LL Pow(LL a, LL b){
	LL sum = 1;
	for(; b; b >>= 1){
		if(b & 1) sum = sum * a % MOD;
		a = a * a % MOD;
	}
	return sum;
}

LL C(int n, int m){if(n == m) return 1; return fac[n] * inv[m] % MOD * inv[n - m] % MOD;}

void Work(){
	fac[0] = 1;
	for(int i = 1; i <= n; i ++) fac[i] = fac[i - 1] * i % MOD;
	inv[n] = Pow(fac[n], MOD - 2);
	for(int i = n - 1; i >= 1; i --) inv[i] = inv[i + 1] * (i + 1) % MOD;
	LL M1 = 1;
	for(int i = 2; i <= n; i += 2) M1 = (M1 + C(n, i)) % MOD;
	LL ans = 0;
	if(!(n & 1))
		for(int i = 1; i <= k; i ++)
			ans = (ans + Pow((M1 + ((n & 1) ? 1 : - 1) + MOD) % MOD, i - 1) * Pow(Pow(2, k - i), n) % MOD) % MOD;
	ans = ((ans + Pow((M1 + ((n & 1) ? 1 : - 1) + MOD) % MOD, k)) % MOD) % MOD;
	
	printf("%lld\n", ans);
}

int main(){
	T = read();
	while(T --){
		n = read(), k = read();
		Work();
	}
	return 0;
}

D

给定 \(n\) 个串,其中某些连续段为 \(1\) 其它为 \(0\),若两行之间有同列均为 \(1\) 则它们可以相邻。

要求删去一些行使得矩阵合法,求最少删除的行数,并给出任意一组方案。

看到数据范围就放弃 DP 的思路了,还是要敢想 + 大力优化。

\(f(i)\) 表示第 \(i\) 行的答案,那么 \(f(i)=\max\limits _{1\leq j<i}\{f(j)\}+1\),要求 \(i,j\) 有对应位。

利用线段树维护列的最大值,需要支持 区间赋值 + 区间求 max,再记录一个 \(pre_i\) 来输出方案。

同行之间先同时 Query 再同时 Modify,防止重叠情况的影响。时间复杂度 \(O(n\log m)\)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;

typedef pair<int, int> PII;
#define MP make_pair
#define X  first
#define Y  second

const int N = 3e5 + 10;
int n, m, t, f[N], pre[N], b[N << 1];
PII dat[N << 3], cov[N << 3], Zero = MP(0, 0);
bool vis[N];
vector<PII> 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;
}

void Push_Down(int p){
	if(cov[p].X == 0) return;
	int l = p << 1, r = p << 1 | 1;
	dat[l] = max(dat[l], cov[p]), cov[l] = max(cov[l], cov[p]);
	dat[r] = max(dat[r], cov[p]), cov[r] = max(cov[r], cov[p]);
	cov[p] = MP(0, 0);
}

void Modify(int p, int l, int r, int L, int R, int v, int Pos){
	if(L <= l && r <= R){
		cov[p] = max(cov[p], MP(v, Pos));
		dat[p] = max(dat[p], MP(v, Pos));
		return;
	}
	Push_Down(p);
	int mid = (l + r) >> 1;
	if(L <= mid) Modify(p << 1, l, mid, L, R, v, Pos);
	if(R >  mid) Modify(p << 1 | 1, mid + 1, r, L, R, v, Pos);
	dat[p] = max(dat[p << 1], dat[p << 1 | 1]);
}

PII Query(int p, int l, int r, int L, int R){
	if(L <= l && r <= R) return dat[p];
	Push_Down(p);
	int mid = (l + r) >> 1;
	if(L >  mid) return Query(p << 1 | 1, mid + 1, r, L, R);
	if(R <= mid) return Query(p << 1, l, mid, L, R);
	return max(Query(p << 1, l, mid, L, R), Query(p << 1 | 1, mid + 1, r, L, R));
}

int main(){
	n = read(), m = read();
	for(int i = 1; i <= m; i ++){
		int p = read(), l = read(), r = read();
		G[p].push_back(MP(l, r));
		b[++ t] = l, b[++ t] = r;
	}
	sort(b + 1, b + t + 1);
	t = unique(b + 1, b + t + 1) - (b + 1);
	for(int i = 1; i <= n; i ++)
		for(int j = 0; j < (int) G[i].size(); j ++){
			G[i][j].X = lower_bound(b + 1, b + t + 1, G[i][j].X) - b;
			G[i][j].Y = lower_bound(b + 1, b + t + 1, G[i][j].Y) - b;
		}
	for(int i = 1; i <= n; i ++){
		f[i] = 0;
		for(int j = 0; j < (int) G[i].size(); j ++){
			PII now = Query(1, 1, t, G[i][j].X, G[i][j].Y);
			if(now.X > f[i])
				f[i] = now.X, pre[i] = now.Y;
		}
		if(!f[i])
			f[i] = 1, pre[i] = i;
		else
			f[i] ++;
		for(int j = 0; j < (int) G[i].size(); j ++)
			Modify(1, 1, t, G[i][j].X, G[i][j].Y, f[i], i);
	}
	int Mx = 0, Pos;
	for(int i = 1; i <= n; i ++) if(f[i] > Mx) Mx = f[i], Pos = i;
	for(; ; Pos = pre[Pos]) if(vis[Pos]) break; else vis[Pos] = true;
	printf("%d\n", n - Mx);
	for(int i = 1; i <= n; i ++) if(!vis[i]) printf("%d ", i);
	puts("");
	return 0;
}

E

国际象棋,要求用一个 Queen\(\leq 130\) 步内将死一个 King

其中 (hidden)King 的位置未知,但是它不能吃 Queen

据说 S 型乱搞就能过 System Test 😮

考虑一行行往下压,直到 King 无处可走。

首先思考一下,若从左往右将一行扫一遍的时候,King 并没有上下移动,那么他肯定离 Queen 至少两行。

然后分解步骤:

  1. 将一行扫一遍。
  2. King 下移就直接下移。
  3. King 全程没有垂直移动就直接下移。
  4. King 上移,它至多移动到 Queen 下面一行,再从新扫一遍,就能够强制他向下移动。

上一操作的次数有限,不难发现至多 \(8\) 次,每次操作扫描需要 \(8\) 步。(将后移动到最左边也算一步)

每次换行也需要一步,至多 \(7\) 次换行,所以总次数 \(\leq 8(8+8)=128<130\)

#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

int nowY;

string Move(int x, int y){
	cout << x << ' ' << y << endl;
	nowY = y;
	string s;
	cin >> s;
	return s;
}

bool Get(int i){
	string s;
	for(int j = (nowY == 1) ? 2 : 1; j <= 8; j ++){
		s = Move(i, j);
		if(s == "Done")
			return true;
		if(s.find("Up") != string::npos)
			return Get(i);
		if(s.find("Down") != string::npos)
			return false;
	}
	return false;
}

void Work(){
	nowY = 1;
	string s;
	for(int i = 1; i <= 8; i ++){
		s = Move(i, nowY);
		if(s == "Done")
			return;
		if(Get(i))
			return;
	}
}

int main(){
	int T; scanf("%d", &T);
	while(T --) Work();
	return 0;
}
posted @ 2021-08-10 20:17  LPF'sBlog  阅读(85)  评论(0编辑  收藏  举报