补题报告:Codeforces Round #767 (Div. 2)

A. Download More RAM

https://codeforces.com/contest/1629/problem/A

Problem

有两个序列 a a a b b b ,开始时你有 k k k 资源,你可以花费 b i b_i bi 资源获得 a i + b i a_i + b_i ai+bi 资源,最多可以到达多少资源?

Solution

贪心。
将序列 a a a b b b b b b 从小到大排序,然后依次购买,一旦遇到 b i > k b_i>k bi>k(也就是买不起的时候)就 b r e a k break break,因为后面的你更买不起 ( b i < = b i + 1 ) (b_i<=b_{i+1}) (bi<=bi+1)

Code

#include<bits/stdc++.h>
using namespace std;

#define PII pair<int, int>
#define x first
#define y second
#define eps 1e-6
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
typedef long long ll;
// #define int long long

const int N = 110;
PII a[N];

bool cmp(PII& a, PII& b){
	if(a.x!=b.x) return a.x<b.x;
	return a.y<b.y;
}

void sol() {
	int n, k;
	cin >> n >> k;
	for (int i=0; i<n; i++) cin >> a[i].x;
	for (int i=0; i<n; i++) cin >> a[i].y;
	sort(a,a+n,cmp);
	for (int i=0; i<n; i++){
		if(k>=a[i].x){
			k+=a[i].y;
		}
	}
	cout << k << '\n';
}

int main(){
	// ios::sync_with_stdio(false);
	// cin.tie(nullptr);

	int T_T=1;
	cin >> T_T;
	while (T_T--) sol();

	return 0;
}

B. GCD Arrays

https://codeforces.com/contest/1629/problem/B

Problem

在序列 [ l , r ] [l,r] [l,r] 上进行至多 k k k 次操作,每次操作可以选择两个数,将这两个数从序列中删掉,然后再向序列中插入这两个数的乘积,序列的 G C D GCD GCD是否能大于 1 1 1 g c d ( a 1 , a 2 , . . . , a r − l + 1 > 1 gcd(a_1,a_2, ... ,a_{r-l+1}>1 gcd(a1,a2,...,arl+1>1

Solution

若序列的 G C D > 1 GCD>1 GCD>1,说明这个序列的每一个数都有一个大于 1 1 1的公因子。题意中的操作,可以理解为,让 a a a获得 b b b的所有因数, b b b获得 a a a的所有因数,那么怎么通过最少操作让所有数都获得同一个因数呢?显然让所有数获得 2 2 2这个因数时,操作次数最少,因为已经有一半的数含这个因数。
通过以上分析,若长度为偶数,只要 k > = ⌊ r − l + 1 2 ⌋ k>= \lfloor \frac {r-l+1} 2 \rfloor k>=2rl+1即可。当长度为奇数时,分两种情况:

  1. l , r l,r l,r为偶数时,假设 l = 4 , r = 6 l=4,r=6 l=4,r=6,序列 [ 4 , 5 , 6 ] [4,5,6] [4,5,6]通过一次操作后变为 [ 20 , 6 ] [20,6] [20,6],此时没操作的" 6 6 6"本身含有因数 2 2 2,因此不需要再进行操作,所以这种情况也是 k > = ⌊ r − l + 1 2 ⌋ k>= \lfloor \frac {r-l+1} 2 \rfloor k>=2rl+1即可
  2. l , r l,r l,r为偶数时,假设 l = 5 , r = 7 l=5,r=7 l=5,r=7,序列 [ 5 , 6 , 7 ] [5,6,7] [5,6,7]通过一次操作后变为 [ 30 , 7 ] [30,7] [30,7],此时没操作的" 7 7 7"不含有因数 2 2 2,因此需要再进行一次操作,序列变为 [ 210 ] [210] [210],所以这种情况是 k > = ⌊ r − l + 1 2 ⌋ + 1 k>= \lfloor \frac {r-l+1} 2 \rfloor + 1 k>=2rl+1+1即可

Code

#include<bits/stdc++.h>
using namespace std;

#define PII pair<int, int>
#define x first
#define y second
#define eps 1e-6
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
typedef long long ll;
// #define int long long

const int N = 110;

void sol() {
	int l,r,k;
	cin >> l >> r >> k;
	if(k==0){
		if(l==r && l!=1) puts("YES");
		else puts("NO");
	}
	else if((r-l+1)%2==0){
		if(k>=(r-l+1)/2) puts("YES");
		else puts("NO");
	}else{
		if(l%2==0) {
			if(k>=(r-l+1)/2) puts("YES");
			else puts("NO");
		}else{
			if(k>=(r-l+1)/2+1) puts("YES");
			else puts("NO");
		}
	}
}

int main(){
	// ios::sync_with_stdio(false);
	// cin.tie(nullptr);

	int T_T=1;
	cin >> T_T;
	while (T_T--) sol();

	return 0;
}

C. Meximum Array

https://codeforces.com/contest/1629/problem/C

Problem

给你一个序列 a a a,和一个空序列 b b b,你可以将 a a a分割为若干连续子序列,每一段子序列得到一个 M E X MEX MEX值,然后放到序列 b b b的末尾,希望得到的序列 b b b的字典序尽可能大。
例如:a=[2 2 3 4 0 1 2 0]分割为两段 [2 2 3 4 0 1] 和 [2 0],他们的 M E X MEX MEX值分别为 5 5 5 1 1 1,所以得到的序列 b b b为[5 1],此时序列 b b b的字典序最大。

Solution

贪心+模拟。

由于 a i a_i ai比较小,我们可以存下来每个数出现的次数。然后从前向后看,为了得到最大的字典序,所以每一段都应得到尽可能大的MEX值。
假设当前位置为 i i i,若 M E X ( a [ i , . . . , j ] ) = 8 MEX(a[i,...,j])=8 MEX(a[i,...,j])=8 M E X ( a [ i , . . . , k ] ) = 7 ( k < j ) MEX(a[i,...,k])=7(k<j) MEX(a[i,...,k])=7(k<j),我不可能到 k k k处停下来,因为继续向后走,这一段的MEX值可以更大。但是我也不能盲目的一直向后走,假设 M E X ( a [ i , . . . , j ] = 8 MEX(a[i,...,j]=8 MEX(a[i,...,j]=8,而 a j a_j aj之后根本没有 9 9 9,那么就算一直向后走,也不可能得到 M E X = 9 MEX=9 MEX=9,这种情况可以通过 c n t [ 9 ] cnt[9] cnt[9]知道后面有没有 9 9 9,这决定了这一段我们是否继续向后走。
基于这两点说明,应该比较容易写出如下代码。

Code

#include<bits/stdc++.h>
using namespace std;

#define PII pair<int, int>
#define x first
#define y second
#define eps 1e-6
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
typedef long long ll;
// #define int long long

const int N = 200010;

void sol() {
	int n;
	cin >> n;
	vector<int> a(n);
	for (int i=0; i<n; i++) cin >> a[i];
	vector<int> cnt(n+1);
	vector<int> v;
	for (int i=0; i<n; i++) cnt[a[i]]++;
	int i = 0;
	while (i<n){
		int mex = 0;
		while (cnt[mex]) mex++;
		vector<bool> st(mex);
		int k = 0;
		while (i<n){
			if(a[i]<mex && !st[a[i]]){
				st[a[i]]=1;
				k++;
			}
			cnt[a[i]]--;
			i++;
			if(k==mex){
				break;
			}
		} 
		v.push_back(mex);
	}
	cout << v.size() << '\n';
	for (auto u : v) cout << u << ' ' ;
	cout << '\n';
}

int main(){
	// ios::sync_with_stdio(false);
	// cin.tie(nullptr);

	int T_T=1;
	cin >> T_T;
	while (T_T--) sol();

	return 0;
}

D. Peculiar Movie Preferences

https://codeforces.com/contest/1629/problem/D

Problem

n n n个长度不超过 3 3 3的字符串中,能否通过若干个字符串的拼接形成一个回文串?(可以是一个字符串的拼接)要求子串之间的相对顺序不变

Solution

思路很容易想到,显然我们不需要那么多的串来组成一个回文串,只需要一个或者两个串即可。首先说下 O ( N 2 ) O(N^2) O(N2)的做法,循环遍历两轮,看 s i s_i si s j s_j sj能否组成回文串。如果用 s e t set set或者 m a p map map集合来存的话,只需要 O ( N l o g N ) O(NlogN) O(NlogN)即可,因为每轮查询的复杂度为 O ( l o g N ) O(logN) O(logN)

Code

T神代码总能让我学到自己陌生的函数。

#include<bits/stdc++.h>
using namespace std;

#define PII pair<int, int>
#define x first
#define y second
#define eps 1e-6
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
typedef long long ll;
// #define int long long

const int N = 200010;

void sol() {
	int n;
	cin >> n;
	vector<string> s(n);
	for (int i=0; i<n; i++) cin >> s[i];
	set<string> p;
	bool ok = 0;
	// 用s[i]和自己匹配
	for (int i=0; i<n; i++){
	    string x = string(s[i].rbegin(),s[i].rend());
	    if(x==s[i]) {
	        ok=1;
	        break;
	    }
	}
	// 用s[i]的后一部分匹配
	for (int i=0; i<n; i++){
		string x = string(s[i].rbegin(),s[i].rend());
		string y = string(s[i].rbegin(),prev(s[i].rend()));
		if(p.find(x)!=p.end() || p.find(y)!=p.end()) {
			ok=1;
			break;
		}
		p.insert(s[i]);
	}
	p.clear();
	// 用s[i]的前一部分匹配
	for (int i=n-1; i>=0; i--){
		string x = string(next(s[i].rbegin()),s[i].rend());
		if(p.find(x)!=p.end()){
			ok=1;
			break;
		}
		p.insert(s[i]);
	}
	if(ok) puts("YES");
	else puts("NO");
}

int main(){
	// ios::sync_with_stdio(false);
	// cin.tie(nullptr);

	int T_T=1;
	cin >> T_T;
	while (T_T--) sol();

	return 0;
}

E. Grid Xor

https://codeforces.com/contest/1629/problem/E

Problem

给你一个矩阵a,希望你利用矩阵a求出矩阵b中各元素的异或。其中 a [ i ] [ j ] a[i][j] a[i][j]的含义为 b [ i − 1 ] [ j ] b[i-1][j ] b[i1][j] ^ b [ i + 1 ] [ j ] b[i+1][j] b[i+1][j] ^ b [ i ] [ j − 1 ] b[i][j-1] b[i][j1] ^ b [ i ] [ j + 1 ] b[i][j+1] b[i][j+1]

Solution

构造题。

类似于“黑白迭代”游戏的解法。先抛开题目,来想这么一个游戏,在一个 n ∗ n n*n nn的网格图中,开始时网格都是白色,每次点击方格 ( i , j ) (i,j) (i,j)时,它的上下左右四个方格都会变成相反的颜色(黑变白,白变黑),怎么把网格图变为全黑呢?有一个通解:枚举第一行的染色方案,从第二行开始,第 j j j个格子是否点击,取决于 ( i − 1 , j ) (i-1,j) (i1,j)是否为黑色,如果是白色,那么第j个格子一定要点击(如果再不操作,那么 ( i − 1 , j ) (i-1,j) (i1,j)将再也不可能变为黑色)。
这个题目和游戏有什么关系呢?其实做一个简单转化,将颜色想成 0 / 1 0/1 0/1,每次点击时,都是对上下左右四个格子的值与 1 1 1异或( 1 1 1 0 0 0 0 0 0 1 1 1),然后将一个全 0 0 0的网格图变为全 1 1 1
然后只需要将所点击过的格子的 a [ i ] [ j ] a[i][j] a[i][j]异或到一起就可以了!
可是 n < = 1000 n<=1000 n<=1000,不允许我们去枚举第一行的状态。所以继续考虑,对于第一行任意状态,上述通解是否还能奏效呢?(下面的证法来自EveruleAnandOza
显然第一行存在某种颜色状态,通过上述通解得到全 1 1 1。假设 ( 1 , x ) (1,x) (1,x)格子未被点击过,将其修改为点击过,那么进而会影响 ( 2 , x + 1 ) , ( 2 , x − 1 ) (2,x+1),(2,x-1) (2,x+1),(2,x1)的点击状态,进而影响 ( 3 , x + 2 ) , ( 3 , x ) , ( 3 , x − 2 ) (3,x+2),(3,x),(3,x-2) (3,x+2),(3,x),(3,x2)的点击状态…然后传递到最后一行。我们发现,通过修改上述格子的点击状态,依然可以保证网格图为全 1 1 1,为什么呢?因为被影响过颜色状态的格子改变了偶数次(注意染色状态和点击状态不同,点击状态会影响上下左右四个格子的颜色状态)
AnandOza
所以第一行任意状态,都可以通过上述通解来得到答案。

呼~终于说完了,虽然有CF大佬的讲解,但本蒟蒻还是看了很久才想明白(总是将点击状态和颜色状态混淆),果然还是太菜了 T_T

Code

#include<bits/stdc++.h>
using namespace std;

#define PII pair<int, int>
#define x first
#define y second
#define eps 1e-6
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
typedef long long ll;
// #define int long long

const int N = 1010;
int g[N][N];
int a[N][N];
int dx[]={0,1,0,-1},dy[]={1,0,-1,0};

void light(int x, int y){
	for (int i=0; i<4; i++){
		int nx=x+dx[i],ny=y+dy[i];
		g[nx][ny]^=1;
	}
}

void sol() {
	int n;
	cin >> n;
	int ans = 0;
	for (int i=1; i<=n; i++) for (int j=1; j<=n; j++) g[i][j]=0;
	for (int i=1; i<=n; i++) for (int j=1; j<=n; j++) cin >> a[i][j];

	for (int i=2; i<=n; i++){
		for (int j=1; j<=n; j++){
			if(!g[i-1][j]){
				light(i,j);
				ans ^= a[i][j];
			}
		}
	}
	cout << ans << '\n';
}

int main(){
	// ios::sync_with_stdio(false);
	// cin.tie(nullptr);

	int T_T=1;
	cin >> T_T;
	while (T_T--) sol();

	return 0;
}

F1. Game on Sum (Easy Version)

https://codeforces.com/contest/1629/problem/F1

Problem

又是Alice和Bob在玩游戏,游戏一共进行 n n n轮,每轮Alice从 0 ∼ k 0 \sim k 0k中选择一个数 x x x,Bob选择将游戏分数加上 x x x或者减去 x x x,但是Bob至少要增加游戏分数m轮。Alice的目的是最大化游戏分数,Bob的目的是最小化游戏分数。
( 1 < = m < = n < = 2000 ) (1<=m<=n<=2000) 1<=m<=n<=2000)

Solution

状态方程 f [ i ] [ j ] f[i][j] f[i][j]表示前i轮中,Bob选择 j j j次增加时的游戏分数。由于Bob要最小化游戏分数,所以 f [ i ] [ j ] = m i n ( f [ i − 1 ] [ j − 1 ] + x , f [ i − 1 ] [ j ] − x ) f[i][j]=min(f[i-1][j-1] + x, f[i-1][j] - x) f[i][j]=min(f[i1][j1]+x,f[i1][j]x),而Alice要最大化游戏分数,所以要最大化 m i n ( f [ i − 1 ] [ j − 1 ] + x , f [ i − 1 ] [ j ] − x ) min(f[i-1][j-1] + x, f[i-1][j] - x) min(f[i1][j1]+x,f[i1][j]x),显然取中间值时是最优选择。(否则Bob永远选择 f [ i − 1 ] [ j − 1 ] + x f[i-1][j-1] + x f[i1][j1]+x f [ i − 1 ] [ j ] − x f[i-1][j] - x f[i1][j]x中较小的一个)

Code

#include<bits/stdc++.h>
using namespace std;

#define PII pair<int, int>
#define x first
#define y second
#define eps 1e-6
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
typedef long long ll;
#define int long long

const int N = 2010;
int f[N][N];

int ksm(int a, int b, int p){
	int res = 1;
	while (b){
		if(b&1) res = res * a % p;
		a = a * a % p;
		b >>= 1;
	}
	return res % p;
}

void sol() {
	int n,m,k;
	cin >> n >> m >> k;
	//初始化
	for (int i=1; i<=n; i++){
		f[i][0]=0;
		f[i][i]=k*i%mod;
	}
	
	for (int i=1; i<=n; i++){
		for (int j=1; j<=m; j++){
			if(i==j) continue;
			if(j==0) continue;
			f[i][j]=(f[i-1][j-1]+f[i-1][j])*ksm(2,mod-2,mod)%mod;
		}
	}
	cout << f[n][m] << '\n';
}

signed main(){
	// ios::sync_with_stdio(false);
	// cin.tie(nullptr);

	int T_T=1;
	cin >> T_T;
	while (T_T--) sol();

	return 0;
}

F2. Game on Sum (Hard Version)

https://codeforces.com/contest/1629/problem/F2

Problem

同F1 ( 1 < = m < = n < = 1 0 6 ) (1<=m<=n<=10^6) (1<=m<=n<=106)

Solution

观察F1中的状态转移方程: f [ i ] [ j ] = ( f [ i − 1 ] [ j ] + f [ i − 1 ] [ j − 1 ] ) / 2 f[i][j]=(f[i-1][j]+f[i-1][j-1])/2 f[i][j]=(f[i1][j]+f[i1][j1])/2,需要求的是 f [ n ] [ m ] f[n][m] f[n][m],如果不考虑除以 2 2 2,是不是很像路径方案数的状态转移方程?的确是这样,与 ( 1 , 1 ) (1,1) (1,1) ( n , m ) (n,m) (n,m)的方案数求法的一点区别不同就是初始值的不同, f [ i ] [ i ] = i ∗ k f[i][i]=i*k f[i][i]=ik(由F1可知),而从 ( i , j ) (i,j) (i,j) ( n , m ) (n,m) (n,m)的方案数为 ( n − 1 m − 1 ) {n-1 \choose m-1} (m1n1)。我们考虑从 ( i , i ) (i,i) (i,i) ( n , m ) (n,m) (n,m)的贡献值即可,贡献值为 ( n − i − 1 m − i ) ∗ f [ i ] [ i ] ∗ 1 2 n − i {n-i-1 \choose m-i} * f[i][i]* \frac 1 {2^{n-i}} (mini1)f[i][i]2ni1 (因为中间状态转移时被除了 n − i n-i ni 2 2 2),将这些贡献值加起来即可。

Code

#include<bits/stdc++.h>
using namespace std;

#define PII pair<int, int>
#define x first
#define y second
#define eps 1e-6
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
typedef long long ll;
#define int long long

const int N = 1000010;
int fac[N],ifac[N],ipw[N];

int ksm(int a, int b, int p){
	int res = 1;
	while (b){
		if(b&1) res = res * a % p;
		a = a * a % p;
		b >>= 1;
	}
	return res % p;
}

void init(){
	fac[0]=1;
	ipw[0]=1;
	ifac[0]=1;
	for (int i=1; i<=1000000; i++) fac[i]=fac[i-1]*i%mod;
	ifac[1000000]=ksm(fac[1000000],mod-2,mod);
	for (int i=999999; i>=0; i--) ifac[i]=ifac[i+1]*(i+1)%mod;
	for (int i=1; i<=1000000; i++) ipw[i]=ipw[i-1]*(mod+1)/2%mod;
}

int C(int n, int m){
	if(n<0||m<0||n<m) return 0;
	return fac[n] * ifac[n-m] % mod * ifac[m] % mod;
}

void sol() {
	int n, m, k;
	cin >> n >> m >> k;
	int ans = 0;
	if(n==m){
		cout << k * m % mod << '\n';
		return;
	}
	for (int i=1; i<=m; i++){
		ans = (ans + C(n-i-1,m-i)*i%mod*k%mod*ipw[n-i])%mod;
	}
	cout << ans << '\n';
}

signed main(){
	// ios::sync_with_stdio(false);
	// cin.tie(nullptr);

	init();
	int T_T=1;
	cin >> T_T;
	while (T_T--) sol();

	return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值