晚训地址链接VJ地址点击即可传送
A题
相当简单的条件判断题
#include<bits/stdc++.h>
using namespace std;
int main(){int t;cin>>t;while(t--){int x;cin>>x;cout<<"Division ";if(x>=1900)cout<<1;else if(x>=1600)cout<<2;else if(x>=1400)cout<<3;else cout<<4;cout<<'\n';}return 0;
}
B题
非常简单的数组的标记应用,观察发现输入的N个元素的值不超过2e5,你可以选择用数组标记,或者用map标记。数组需要每组数据处理完后清空一遍,map也需要。map的清空是map名字.clear(); 例如vis.clear();
#include<bits/stdc++.h>
using namespace std;
int vis[200005];
int main(){int t;cin>>t;while(t--){int ans=-1;//初始值int n;cin>>n;for(int i=1;i<=n;i++){int x;cin>>x;vis[x]++;if(vis[x]==3)ans=x; } cout<<ans<<'\n';memset(vis,0,sizeof(vis));}return 0;
}
C题
每次只能对奇数下标或者偶数下标的所有数+1,那么很明显,如果你想让最终所有数奇偶性一致,必须保证所有奇数下标位置的数 以及偶数下标位置的数,奇偶性一致。为什么呢?因为全是偶数的话再+1就变全是奇数了。所以说如果一开始,奇数下标的所有数奇偶性一样并且偶数下标的数也满足,那么就是YES
#include<bits/stdc++.h>
using namespace std;
int A[55];
int main(){int t;cin>>t;while(t--){int n;cin>>n;int ok=0;int op=0;int y=1;int x=1;for(int i=1;i<=n;i++){cin>>A[i]; if(i==1)ok=A[i]%2;if(i==2)op=A[i]%2;if(i>=3&&i%2==0){if(A[i]%2!=op)y=0;}if(i>=3&&i%2==1){if(A[i]%2!=ok)x=0;} }if(x+y<=1)cout<<"NO";else{cout<<"YES";}cout<<'\n'; }return 0;
}
D题
我们可以自己先玩一玩这个题目
盖一次能产生RB或者BR
盖两次能产生RBRB BRBR RBB BRR
我们重点关注RBB BRR 你会发现再盖一次能变成
RBBB BRRR 观察规律发现,不管生成的是个什么字符串,如果满足这一整段有R有B那么必定就是能盖出来的,否则不可能。
举例WRRRRRRRRRW 或者WBBBBBBBBBBBW
所以说我们需要判断当你遇到W时,你的上一段是不是有R有B或者无R无B,为了防止最后一段出现漏判的情况,我直接修改了字符串最后一位为W
#include<bits/stdc++.h>
using namespace std;
char s[100005];
int main(){int t;cin>>t;while(t--){int n;cin>>n;cin>>s+1;int R=0;int B=0;s[n+1]='W';n++;int ok=1;for(int i=1;i<=n;i++){if(s[i]=='W'){if(R!=0&&B!=0||R+B==0){R=0;B=0;continue;} else{ok=0;break; }}if(s[i]=='R')R++;if(s[i]=='B')B++;}if(ok)cout<<"YES";else cout<<"NO";cout<<'\n';}return 0;
}
E题
其实原理类似于N个小朋友,每个小朋友都要跟其余小朋友握手,问有多少次不同的握手。简单的思想就是枚举第i个小朋友,再枚举第[i+1,N]小朋友与之握手,为什么不枚举前面的人呢?因为前面的小朋友已经跟i握手过了,不再产生贡献。所以说本题我们可以这样子思考。
首先这个题的字符串长度为2,我们可以记录每种字符串的个数
利用数组标记,例如vis[第一个字符][第二个字符] ++ ;
这里涉及到字母转换成“第几个字母”数字
例如我们查询vis[1][2]实际上就是查ab字符串的个数
做好统计以后,按字典序枚举字符串
for(int i=1;i<=26;i++)for(int j=1;j<=26;j++)
接下来枚举与它恰好有一个位置不同的字符串
实际上有两种,因为此时我们枚举的字符串是ij (第一个字母是第i个字母,第二个字母是第j个字母),与它恰好只有一个不同的字符串是
ik(k不等于j) kj(k不等于i)这两种 ,那么根据乘法原理,计数即可
计数完毕以后我们要把vis[i][j] 这种字符串的个数清零,这就是“握手思想”,ij字符串的贡献已经计算完毕,后面必须独立于其余计算,否则会计算重复
#include<bits/stdc++.h>
using namespace std;
long long int pre[28][28];
int main() {int t;scanf("%d",&t);while(t--) {int n;scanf("%d",&n);memset(pre,0,sizeof(pre));for(int i=1; i<=n; i++) {char a[5];scanf("%s",a+1);pre[a[1]-'a'+1][a[2]-'a'+1]++;}long long int ans=0;for(int i=1; i<=26; i++) {for(int j=1; j<=26; j++) {long long int s=0;for(int k=1; k<=26; k++) {if(j==k)continue;s=s+pre[i][k];}ans=ans+pre[i][j]*s;s=0;for(int k=1; k<=26; k++) {if(i==k)continue;s=s+pre[k][j];}ans=ans+pre[i][j]*s;pre[i][j]=0;}}printf("%lld\n",ans);}return 0;
}
F题(个人认为比E题简单)
实际上有好好订正晚训的同学这个题肯定会做的,这里我要再次强调订正晚训题目的重要性
本题就是一个两端往中间吃的过程,不难发现,因为最终要使得两个人吃的数量是相同的,那么双方有不平衡的时候,就应该让弱势方继续吃,平衡的时候保存一遍答案。(双指针的思想)
当然你可以用枚举+标记来做
比如枚举一个前缀和,把所有后缀和标记起来
枚举某个前缀和位置在i,值是s,查找后缀和标记里面有没有跟这个s值一样的后缀和,如果有看看它的位置是不是>i
本题我用双指针控制写的
#include<bits/stdc++.h>
using namespace std;
int A[200005];
int main() {int t;cin>>t;while(t--) {int n;cin>>n;int ans=0;int L=1;int R=n;for(int i=1; i<=n; i++) {cin>>A[i];}int eat_L=0;int eat_R=0;int op=0;int eat=0;while(true) {if(eat==n)break;if(op==0) {eat_L+=A[L];L++;eat++;if(eat_L==eat_R) {ans=eat;op=(op+1)%2;continue;}if(eat_L>eat_R) {op=(op+1)%2;continue;}}if(op==1) {eat_R+=A[R];R--;eat++;if(eat_L==eat_R) {ans=eat;op=(op+1)%2;continue;}if(eat_L<eat_R) {op=(op+1)%2;continue;}}}cout<<ans<<'\n';}return 0;
}
G题
一个简单的模拟游戏而已,模拟出石头掉落,最终落在哪里,注意一开始可能有些石头是悬空的,如果你从上往下枚举去处理石头的掉落,会出现上面的石头掉落在下面悬空着的石头上面。所以考虑到物理意义,我们从下往上枚举去处理石头
#include<bits/stdc++.h>
using namespace std;
char s[52][52];
int n,m;
void check(int x,int y){while(true){//石头向下落,要么在边界,要么在障碍上面,要么在别的石头上面 //但是要从下往上枚举,不然石头可能掉在悬空的另一个石头上 if(x==n)return ;//在底部了 if(x+1<=n&&s[x+1][y]=='.'){s[x][y]='.';s[x+1][y]='*';x++;continue;//判断能不能下落 }return ;//程序能走到这说明没掉下去 }
}
int main() {int t;cin>>t;while(t--) {cin>>n>>m;for(int i=1;i<=n;i++){cin>>s[i]+1;}for(int i=n;i>=1;i--){for(int j=1;j<=m;j++){if(s[i][j]=='*')check(i,j);}}for(int i=1;i<=n;i++){cout<<s[i]+1<<'\n';}}return 0;
}
H题
补充一下位运算的知识,位运算指的是对数字的二进制上对应的每一位去做算,常见的位运算符有以下几种
&与运算AND
计算规则就是A&B ,先把A B拆解成二进制形式例如A=5 B=4
那么就是00101(表示5) 从左到右分别是20 21 22 …单位,类似于十进制
00101
&
00100
对应位置的0 1 做运算然后掉下来,&的逻辑就是而且,同时为1结果才为1,所以00101&00100=00100
|或运算 OR 逻辑类似于上面讲的,只不过它是执行或者的逻辑,有一个为1则是1
^异或运算 XOR 逻辑是相同变0,不同变1
好了开始本题正式讲解
一开始给了我们N个数,以及K次机会,一次机会我们可以选择一个数,给它的某个二进制位上OR上一个2j ,实际上相当于你一个数的二进制里面如果2j 这个位置上的系数是0,那么数字OR2j 相当于增加了2j ;数的二进制里面如果2j 这个位置上的系数是1,那么数字OR2j 相当于什么也没变,白白浪费了一次机会。
题目要问K次操作以后,所有数求AND的最大值,我们讲过AND必须是都是1,结果才为1.我们按位思考,N个数的二进制位2j 必须全都有,那么我们答案的数字才有2j ,因为要使得答案最大化,所以说我们可以考虑枚举二进制位 p=[30~0] 从高位开始处理,计算出N个数字里面有多少个数没有2p ,假设有M个数没有,如果M小于等于当前还剩的机会,那么我们就能把这个2p 添加到答案里面去,需要消耗M次机会,如果不够,那么我们也不要浪费机会了,看看下一位能不能添加进去
#include<bits/stdc++.h>
using namespace std;
int A[40];
int mi[35];
int main(){int t;cin>>t;mi[0]=1;//表示2的0次方for(int i=1;i<=30;i++){mi[i]=mi[i-1]*2;//计算2的i次方 后面要用到 } while(t--){int n,k;cin>>n>>k;for(int i=1;i<=n;i++){int x;cin>>x;int cur=0;//表示2的cur次方,拆分数字,统计一共有多少个数具有2的cur次方while(x!=0){//拆分二进制原理跟十进制拆掉是一样的 if(x%2==1){A[cur]++;} cur++;x=x/2;} } int ans=0; for(int i=30;i>=0;i--){if(n-A[i]<=k){ans=ans+mi[i];k=k-(n-A[i]);//A[i]表示N个数里面有多少个数具有2的i次方 } A[i]=0;//清空 } cout<<ans<<'\n'; }return 0;
}