2020-2-15模拟赛题解 前言 正文
这场比赛我发挥欠佳,各种小失误不断,也需要我自己去反思吧,以后打模拟赛也要有选择性地去打,毕竟一天 (3) 场比赛肯定会有影响。
正文
1. 子数整数
错点
考试的时候蒟蒻把 No
打成了 NO
。
分析
这道题实际上就是个小模拟,您只要会 (for) 循环就可以轻松过掉此题。
答案是直接统计一下就好了。
我们可以直接从 10000
到 30000
循环,算出 s1
、 s2
、 s3
。
for(int i=10000;i<=30000;i++){
int g=i%10,s=i/10%10,b=i/100%10,q=i/1000%10,w=i/10000%10,s1=100*w+10*q+b,s2=100*q+10*b+s,s3=100*b+s*10+g;//s1,s2,s3的计算
if(s1%k==0&&s2%k==0&&s3%k==0)writen(i),f=1;
}
总代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &FF){
T RR=1;FF=0;char CH=getchar();
for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
FF*=RR;
}
template<typename T>void write(T x){
if(x<0)putchar('-'),x*=-1;
if(x>9)write(x/10);
putchar(x%10+48);
}
template<typename T>void writen(T x){
write(x);
puts("");
}
int k,s1,s2,s3,g,s,b,q,w;
bool f;
int main(){
read(k);
for(int i=10000;i<=30000;i++){
int g=i%10,s=i/10%10,b=i/100%10,q=i/1000%10,w=i/10000%10,s1=100*w+10*q+b,s2=100*q+10*b+s,s3=100*b+s*10+g;//s1,s2,s3的计算
if(s1%k==0&&s2%k==0&&s3%k==0)writen(i),f=1;
}if(f==0)puts("No");//考试时这里直接挂掉,打成了NO
return 0;
}
2. 安全逃离
错点
蒟蒻向老师报告题目有问题后没有认真看题,结果就死了。
分析
做到这道题的时候,我们考到数据范围很小,考虑高复杂度的做法。
我的做法是最暴力的做法,复杂度大概是 (O(cr^2 imes + rc^2 imes n)),面对这么小的这么小的数据,这个做法显然能过。
我们先写一个 (check) 函数,判断此时的矩阵是否安全。
bool check(){
for(int i=1;i<=r;++)
for(int j=1;j<=c;j++)
if(a[i][j]){//此点有牛
int s=0;
for(int l=1;l<=i;l++)
if(a[l][j]){
s++;
break;
}
for(int l=j;l<=n;l++)
if(a[i][l]){
s++;
break;
}
if(s==2)return false;//如果有牛上面和右面都被堵死了,这个矩阵显然是不安全的
}
return true;
}
当然还有更快速地写法啦
bool save(){
for(int i=1;i<=k;i++){
int flag=0;
for(int l=x[i]-1;l>=1;l--)
if(a[l][y[i]]){
flag++;
break;
}
for(int l=y[i]+1;l<=n;l++)
if(a[x[i]][l]){
flag++;
break;
}
if(flag==2)return false;
}
return true;
}
我们再写一个函数去枚举移掉哪头牛
void work(){
int f=1;
for(int i=1;i<=k;i++){
a[x[i]][y[i]]--;
if(save()){//如果安全
f=0;
write(i);//输出
puts("");
}
a[x[i]][y[i]]++;
}if(f)puts("-1");//无解
}
总代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &FF){
T RR=1;FF=0;char CH=getchar();
for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
FF*=RR;
}
template<typename T>void write(T x){
if(x<0)putchar('-'),x*=-1;
if(x>9)write(x/10);
putchar(x%10+48);
}
const int MAXN=50+10,MAXK=100+10;
int a[MAXN][MAXN],n,m,k,x[MAXK],y[MAXK];
bool save(){
for(int i=1;i<=k;i++){
int flag=0;
for(int l=x[i]-1;l>=1;l--)
if(a[l][y[i]]){
flag++;
break;
}
for(int l=y[i]+1;l<=n;l++)
if(a[x[i]][l]){
flag++;
break;
}
if(flag==2)return false;
}
return true;
}
void work(){
int f=1;
for(int i=1;i<=k;i++){
a[x[i]][y[i]]--;//移除
if(save()){
f=0;
write(i);
puts("");
}
a[x[i]][y[i]]++;//注意回溯
}if(f)puts("-1");
}
int main(){
read(n);read(m);read(k);
for(int i=1;i<=k;i++){
read(x[i]);read(y[i]);
a[x[i]][y[i]]++;
}
if(save())puts("0");
else work();
return 0;
}
3. 阶乘问题
错点
蒟蒻竟然把 (/ 10) 打成了 (\% 10)。
分析
做法显然
由于数字极大,我们应该先对 (1)~(n) 的这些数字把末尾的 (0) 全部删掉
while(x%10==0)x/=10;
再把乘好后的 (s) 末尾的 (0) 也都去掉。
关于为什么末尾会有 (0),我来举个例子 (2 imes 5=10) ,所以我们还要再筛一遍 (0)。
while(s%10==0)s/=10;
总代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &FF){
T RR=1;FF=0;char CH=getchar();
for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
FF*=RR;
}
template<typename T>void write(T x){
if(x<0)putchar('-'),x*=-1;
if(x>9)write(x/10);
putchar(x%10+48);
}
int main(){
int n,s=1;
read(n);
for(int i=1;i<=n;i++){
int x=i;
while(x%10==0)x/=10;
s*=x;
while(s%10==0)s/=10;
}write(s);
return 0;
}
4. 拱猪计分
分析
典型的大模拟。
明白了什么叫 (1h) 读题,(0.2h) 写代码
我们从读入开始
读入
for(int i=1;i<=4;i++){
read(size[i]);
for(int j=1;j<=size[i];j++){
char k;int p;
cin>>k>>p;
if(k=='H')card[i][p]=true;
else if(k=='S')card[i][14]=true;
else if(k=='D')card[i][15]=true;
else if(k=='C')card[i][16]=true;
}
}
我们用 (card_{i,j}) 记录第 (i) 个人,有没第 (j) 张得分牌。
计分
for(int i=1;i<=4;i++){
bool flag=true;
for(int j=1;j<=13;j++)
if(card[i][j]==false){
flag=false;
break;
}
if(flag){//有没有红心牌
int ans=200;//所有红心牌以+200分计算。
if(card[i][14]&&card[i][15])ans=500;//若S12、D11皆在吃下所有红心牌之一家,则此玩家得+500分。
else ans=ans+card[i][14]*fraction[14]+card[i][15]*fraction[15];
if(card[i][16])ans*=2;//若除了C10还有其它计分牌,则将其它计分牌所得分数加倍计算。
work(ans);//输出
putchar(32);//输出
}else{
bool flg=true;
for(int j=1;j<=15;j++)
if(card[i][j]==true){
flg=false;
break;
}
if(flg){
if(card[i][16])work(50);//若持有C10的玩家只有该张牌而没有任何其它牌则得+50分
else work(0);//若未持有这16张牌之任一张则以得零分计算。
putchar(32);//输出
}else{
int ans=0;
for(int j=1;j<=15;j++)
if(card[i][j])ans+=fraction[j];
if(card[i][16])ans*=2;
work(ans);//输出
putchar(32);//输出
}
}
}puts("");
总代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &FF){
T RR=1;FF=0;char CH=getchar();
for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
FF*=RR;
}
template<typename T>void write(T x){
if(x<0)putchar('-'),x*=-1;
if(x>9)write(x/10);
putchar(x%10+48);
}
void work(int x){
if(x<0)cout<<x;
else if(x>0)cout<<'+'<<x;
else cout<<x;
}
const int fraction[17]={0,-50,-2,-3,-4,-5,-6,-7,-8,-9,-10,-20,-30,-40,-100,+100,0};
int size[5];
bool card[5][17];
int main(){
while(1){
memset(card,false,sizeof(card));
for(int i=1;i<=4;i++){
read(size[i]);
for(int j=1;j<=size[i];j++){
char k;int p;
cin>>k>>p;
if(k=='H')card[i][p]=true;
else if(k=='S')card[i][14]=true;
else if(k=='D')card[i][15]=true;
else if(k=='C')card[i][16]=true;
}
}
if(size[1]+size[2]+size[3]+size[4]==0)return 0;
for(int i=1;i<=4;i++){
bool flag=true;
for(int j=1;j<=13;j++)
if(card[i][j]==false){
flag=false;
break;
}
if(flag){
int ans=200;
if(card[i][14]&&card[i][15])ans=500;
else ans=ans+card[i][14]*fraction[14]+card[i][15]*fraction[15];
if(card[i][16])ans*=2;
work(ans);
putchar(32);
}else{
bool flg=true;
for(int j=1;j<=15;j++)
if(card[i][j]==true){
flg=false;
break;
}
if(flg){
if(card[i][16])work(50);
else work(0);
putchar(32);
}else{
int ans=0;
for(int j=1;j<=15;j++)
if(card[i][j])ans+=fraction[j];
if(card[i][16])ans*=2;
work(ans);
putchar(32);
}
}
}puts("");
}
return 0;
}
5. 变色龙
错点
(bfs) 的 (priority)_(queue) 优化写假了。
分析
最短路问题。
为什么能使用 (dij) 跑最短路?明显会 (T) 啊!
点数:(2000 imes 2000=400,0000)
边数:小学奥数之等差数列
(egin{matrix}underbrace{0+4+cdots+7996}\2000项end{matrix}=(7996+0) imes 2000 div 2=7996 imes 1000=799,6000)
我们知道 (dij) 的复杂度是 (O(n log m))
简单计算一下
(log m=log 7996000 approx 26),(26 imes 4000000=1,0400,0000 > 1,0000,0000)
所以显然是会 (T) 掉的(除非玄学卡常,比如:等式展开之类的)
而 (bfs) 的期望复杂度是 (O(n+m)) 的。
(bfs) 大概就是这种写法
q.push((node){xx,yy,0});
while(q.size()){
node f=q.top();
q.pop();
for(int i=0;i<4;i++){
int x=f.x+dx[i],y=f.y+dy[i];
if(x>=1&&x<=n&&y>=1&&y<=m){
int as=ans[f.x][f.y];
if(a[x][y]!=a[f.x][f.y])as++;
if(as<ans[x][y])ans[x][y]=as,q.push((node){x,y});
}
}
}
总代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &FF){
T RR=1;FF=0;char CH=getchar();
for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
FF*=RR;
}
template<typename T>void write(T x){
if(x<0)putchar('-'),x*=-1;
if(x>9)write(x/10);
putchar(x%10+48);
}
struct node{
int x,y;
};
const int MAXN=2000+10;
int n,m,xx,yy,a[MAXN][MAXN],ans[MAXN][MAXN];
int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};
queue<node>q;
int main(){
read(n);read(m);read(xx);read(yy);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
read(a[i][j]);
q.push((node){xx,yy,0});
while(q.size()){
node f=q.top();
q.pop();
for(int i=0;i<4;i++){
int x=f.x+dx[i],y=f.y+dy[i];
if(x>=1&&x<=n&&y>=1&&y<=m){
int as=ans[f.x][f.y];
if(a[x][y]!=a[f.x][f.y])as++;
if(as<ans[x][y])ans[x][y]=as,q.push((node){x,y});
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
cout<<ans[i][j]<<" ";
cout<<endl;
}
return 0;
}
6. 和谐分组
讲解二分
终于是正常一点的题目了,并且是我最喜欢的算法(蒟蒻最喜欢的算法是并查集、二分、dp、LCA)
二分是确定一个答案然后对其分析,而答案常常有这样一种情况:
左闭右开
右闭左开
题目通常会让我们找符合条件的最大值或最小值。
以这道题为例,就是去在可能的谐度度中找一个最小的。
比答案大的都可以,不答案小的都不可以。
这个我们叫右闭左开。
而比答案小的都可以,不答案大的都不可以。
这个我们叫左闭右开。
二分顾名思义,就是二分。
左闭右开:
int l=0,r=INT_MAX/2;
while(l+1<r){
int mid=(l+r)>>1;
if(check(mid))l=mid;//这里不同
else r=mid;//这里不同
}
右闭左开:
int l=0,r=INT_MAX/2;
while(l+1<r){
int mid=(l+r)>>1;
if(check(mid))r=mid;//这里不同
else l=mid;//这里不同
}
分析
现在我们已经会了二分,我们可以愉快地做这道题了。
我们可以开始写 (check) 函数:
bool check(int x){//check函数顾名思义,就是用来反对我们的答案x是否可行
int maxn=a[1],minn=a[1],s=1;//初始化
for(int i=2;i<=n;i++){
maxn=max(maxn,a[i]);//最大
minn=min(minn,a[i]);//最小
if(maxn-minn>x){
s++;//新分一个组
maxn=a[i];//初始化
minn=a[i];//初始化
}
}
return s<=k;//如果组数小于等于k,就说明不可行
}
总代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &FF){
T RR=1;FF=0;char CH=getchar();
for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
FF*=RR;
}
template<typename T>void write(T x){
if(x<0)putchar('-'),x*=-1;
if(x>9)write(x/10);
putchar(x%10+48);
}
const int MAXN=1e5+10;
int n,k,a[MAXN];
bool check(int x){
int maxn=a[1],minn=a[1],s=1;
for(int i=2;i<=n;i++){
maxn=max(maxn,a[i]);
minn=min(minn,a[i]);
if(maxn-minn>x){
s++;
maxn=a[i];
minn=a[i];
}
}
return s<=k;
}
int main(){
read(n);read(k);
for(int i=1;i<=n;i++)read(a[i]);
int l=0,r=INT_MAX/2;
while(l+1<r){
int mid=(l+r)>>1;
if(check(mid))r=mid;
else l=mid;
}write(r);
return 0;
}