Codeforces Round 960 (Div. 2)A~D

A - Submission Bait

解题思路

​ 这个题只需要找出什么情况下先手必胜,后手必败。如果某一个数奇数个,其他的都是偶数个,那么先手肯定要选奇数个那个数,因为谁先遇到全是偶数的局面谁就必失败。所以在最开始有奇数个的局面,先手就可以将必败的局面抛给后手。

AC代码

#include <bits/stdc++.h>

using namespace std;

void solve () {
	int n;
	int a[51] = {0};
	cin>>n;
	for(int i = 1;i <= n;i++) {
		int k;
		cin>>k;
		a[k] ++;
	}
	for(int i = 50;i >= 1;i--) {
		if(a[i] != 0 && a[i] % 2!= 0) {
			cout<<"yes"<<endl;
			return ;
		}
	}
	cout<<"no"<<endl;
}

int main () {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t;
	cin>>t;
	while(t--) {
		solve();
	}
	return 0;
} 

总结

这次是被题目中的操作给绕住了,开始想的就是找出最大值的数量,并判断是否是奇数,并且当时想的是先手要从最大的开始挑选,有点局限,选手都是可以任意挑选的,他的目的就是为后手构造出必败的局面,那就要看什么局面是必败的局面,先手是否能够通过某种操作为后手构成这种必败局面即可。

B - Array Craft

解题思路

​ 根据题目的x和y的范围可判断他俩进行前缀和计算的时候一定会有公共部分,那么怎么将这个公共部分最大化呢?全部构造成 1 ,那么接下来分别看前后缀剩余的部分,先说前缀,那么在当前位置以后的前缀和一定都是小于等于当前位置的,那么剩余部分在构建的时候该怎么来安排呢?首先不能出现1,如果有1就出现了最大前缀右移的状况;可不可以出现多个-1?也不行,如果都是-1 会出现和最大前缀相同而且下标更小的位置,这样就不符合题意了,那么就是0了,可以通过-1/1来交替构造,但是不能用1/-1,这样会导致最大前缀或后缀向后或向前移一位,就有不符合题意了。在构造的过程中分别是从x 的右边和y 的左边进行-1/1构造。

AC代码

#include <bits/stdc++.h>

using namespace std;

void solve () {
	int n,x,y;
	cin>>n>>x>>y;
	int a[n+1] = {0};
	for(int i = y;i <= x;i++) {
		a[i] = 1;
	}
	for(int i = y-1;i >= 1; i--) {
		if((y-i) % 2) {
			a[i] = -1;
		}else { 
			a[i] = 1;
		}
	}
	for(int i = x+1;i <= n;i++) {
		if((i-x) % 2) {
			a[i] = -1;
		}else {
			a[i] = 1;
		}
	}
	for(int i = 1;i <= n;i++) {
		cout<<a[i]<<" ";
	}
	cout<<endl;
}

int main () {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t;
	cin>>t;
	while(t--) {
		solve();
	}
	return 0;
} 

总结

​ 构造题首先要明确的就是哪些可以固定,哪些需要循环添值。找到这些之后就要看要根据什么条件来进行构造,要符合哪些要求?

C - Mad MAD Sum

解题思路

​ 纯纯 暴力 + 找规律,虽然我也做不出来。

​ 首先根据样例来模拟一次之后,数列就变成了非递减的,然后再模拟一次,就发现数列再模拟就变成了右移操作,那么最后右移操作的总和计算可以直接通过公式来计算,如果模拟完之后就是一个右上三角的形状,那么就可以通过第一行来计算出所有的总和。

AC代码

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 2e5+10;
ll a[N];
map<int,int> mp;

void solve () {
	mp.clear();
	ll sum = 0;
	int n;
	cin>>n;
	ll mx = 0;
	for(int i = 0;i < n;i++) {
		cin>>a[i];
		sum += a[i];
		mp[a[i]] ++;
		if(mp[a[i]] >= 2 && mx <= a[i]) {
			mx = a[i];
		}
		a[i] = mx;
	}
	mx = 0;
	mp.clear();
	for(int i = 0;i < n;i++) {
		sum += a[i];
		mp[a[i]]++;
		if(mp[a[i]] >= 2 && mx <= a[i]) {
			mx = a[i];
		}
		a[i] = mx;
 	}
 	for(int i = 0;i < n;i++) {
 		sum += (n-i) * a[i];
	 }
	 cout<<sum<<endl;
}

int main () {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t;
	cin>>t;
	while(t--) {
		solve();
	}
	return 0;
} 

总结

​ 对于这种循环操作的题目,最好的开口就是通过模拟操作来找出操作的规律,一两次可能找不出来,多模拟几次。

D - Grid Puzzle

解题思路

​ 分情况讨论 + dp + 贪心

对于所有的 a i a_i ai有三种情况:

  • 全都大于等于2:那么最少进行2次1操作,最多进行2 次2操作,全选2操作,结果为n;
  • 全都小于等于2:直接模拟1操作即可,不行用2操作。
  • 啥都有:
    • 最坏情况就是执行n次2操作,所以执行2操作作为默认操作,在此基础上再通过1操作进行优化即可,状态转移方程:dp[i] = dp[i-1] + 1。
    • 当a[i] == 0 时,dp[i] = dp[i-1];
    • 当a[i] > 2 时,直接使用默认操作;
    • 当a[i] <= 2 时,判断当前位置到上一个长度小于等于2的位置距离是否是偶数,并且这区间中是否都是小于等于4的,因为这样判断是要通过在两次操作以内完成多行的操作,dp[i] = dp[i-1] + i - j。

AC代码

#include <bits/stdc++.h>

using namespace std;

const int N = 2e5+10;
int n;
int a[N],dp[N]; 

void solve () {
	cin>>n;
	int f = 0;
	for(int i = 1;i <= n;i++) {
		cin>>a[i];
		if(a[i] <= 2) f++;
	}
	if(f == 0) {
		cout<<n<<endl;
		return;
	}
	if(f == n) {
		int ans = 0;
		for(int i = 1;i <= n;i++) {
			if(a[i] == 0) continue;
			if(a[i] > 0 && a[i+1] > 0) {
				ans ++;
				i++;	// 将下一行给跳过 
				continue;
			}
			ans ++;
		}
		cout<<ans<<endl;
		return;
	}
	int j = 0,maxn = 0;
	for(int i = 1;i <= n;i++) {
		dp[i] = 1e9;
	} 
	dp[0] = 0;
	for(int i = 1;i <= n;i++) {
		if(a[i] == 0) {
			dp[i] = dp[i-1];
		}
		dp[i] = min(dp[i],dp[i-1] + 1);
		if(a[i] <= 2 && j && maxn <= 4 && (i-j+1) % 2 == 0){
			dp[i] = min(dp[i],dp[j-1] + i - j);
		}
		if(a[i] <= 2) {
			j = i;
			maxn = 0;
		} else {
			maxn = max(a[i],maxn);
		}
	}
	cout<<dp[n]<<endl;
}

int main () {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t;
	cin>>t;
	while(t--) {
		solve();
	}
	return 0;
} 

总结

​ 这种题就是多种选择找最优,那么就该分情况讨论,不同操作会带来什么,会改变什么,什么时候执行这种操作最好,最后找到最优解,涉及到了动态规划的思想。

这次也是心血来潮一下子不到了第四题,比赛时候是一点状态都没有,一点写不下去,还提交错了两次。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值