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;
}
总结
这种题就是多种选择找最优,那么就该分情况讨论,不同操作会带来什么,会改变什么,什么时候执行这种操作最好,最后找到最优解,涉及到了动态规划的思想。
这次也是心血来潮一下子不到了第四题,比赛时候是一点状态都没有,一点写不下去,还提交错了两次。