例题
例题4-1 古老的密码
走这
问题分析:
- 看了半天没看懂题目,看评论才懂一一映射是什么意思,也就是每个字母以任意规则对应另一个字符串的字母,但每个字母的对应规则不同
- 所以题目就变成出现两个字符串出现相同次数的字母的种类是否相同
AC代码:
#include <bits/stdc++.h>
using namespace std;
int a[101];
int b[101];
int main()
{
/*题目不一定是每个字母都必须向后移动n位,也就是说,字母的映射关系是任意的,但是一种1
字母的映射关系已经确定就无法改变
一一映射:不同元素在映射中有不同的像*/
string s,m;
while(cin>>s>>m)
{
fill(a,a+101,0);
fill(b,b+101,0);
bool flag = true;
for(int i=0;i<26;i++)
{
int x = count(s.begin(),s.end(),'A'+i);
a[x]++;//统计不同字母的出现次数
}
for(int i=0;i<26;i++)
{
int y = count(m.begin(),m.end(),'A'+i);
b[y]++;//统计不同字母的出现次数
}
for(int i=0;i<=99;i++)
{
if(a[i]!=b[i])
{
flag = false;
printf("NO\n");
break;
}
}
if(flag)
printf("YES\n");
}
}
例题4-2 刽子手游戏
走这
问题分析:
- 简单题,只注意将猜对字母在原单词出现几次就加上几,还有就是可能猜错了还在猜或者猜对了还在猜,因为我的方法是一个个读取,但这里显然一次性读取更好
- 用了数组统计猜字母的种类方便判断是否猜,但有更好的方法,就是将猜对字母在原单词中修改成空格,怎么下次猜同一单词必错
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 101;
char s1[maxn],s2[maxn];
int win,lose = 0,chance = 7,lefts;
void guess(char c)
{
int bad = 1;//假设猜错
for(int i=0;i<strlen(s1);i++)
{
if(c==s1[i]) {lefts--;bad = 0;s1[i]=' ';};//这样处理,相同会一起处理
} //将已经猜对的字符改成空格,这样就不用设置计数的数组,因为下次猜必错
if(bad) chance--;//错因=写成了==
if(!chance) lose = 1;
if(!lefts) win = 1;
}
int main()
{
// freopen("in.txt","r",stdin);
int rnd = 0;
while(scanf("%d%s%s",&rnd,s1,s2)==3&&rnd!=-1)//与我的方法不同,这个一次性读入所有数据,在处理已经猜对的数据时
//就不用特点等输入完再判断
{
printf("Round %d\n",rnd);
win = 0,lose = 0;//全局变量初始化
lefts = strlen(s1);
chance = 7;
for(int i=0;i<strlen(s2);i++)
{
guess(s2[i]);
if(win||lose) break;//当猜对或猜错就即时跳出
}
if(win) printf("You win.\n");
else if(lose) printf("You lose.\n");
else printf("You chickened out.\n");
}
}
例题4-3 The Dole Queue
走这
问题分析:
- 约瑟夫环类问题,有区别的是有两个指针,因为序号是从1开始,按我的思路是先将序号-1再取余的,但是紫书利用+1-1就不必担心n%n=0的问题
#include <bits/stdc++.h>
using namespace std;
const int maxn = 25;
int n,k,m,a[maxn];
/*还是要自己写一下再看答案,否则不知道哪不会*/
int leave(int id,int p,int t)//下次数是从出列的下一个人
{//这样更容易处理下次开始的人的位置
while(t--){
do{
p = (p+id+n-1)%n+1;//下标是从1开始的,因此当p+id==10,就会出现a[0]的情况
}while(a[p]==0);//如果是0就重来
}//do-while的运用不用特地从p+1处开始
return p;
}
int main()
{
while(scanf("%d%d%d",&n,&k,&m)==3&&n!=0)
{
for(int i=0;i<maxn;i++) a[i] = i;//用0表示已经出去的
int p1 = n,p2 = 1;//官员是从每次所指的下一位开始数,但第一次数时所指的还没有被
//淘汰,所以各自左移或者右移一位
int lefts = n;
while(lefts)
{
p1 = leave(1,p1,k);//k作为步长
p2 = leave(-1,p2,m);
printf("%3d",p1); lefts--;//方法里要用到n
if(p1!=p2) {printf("%3d",p2);lefts--;};
a[p1] = a[p2] = 0;
if(lefts) printf(",");
}
printf("\n");
}
}
太妙了完全想不到
例题4-4(x)
Message Decoding
走这
问题分析:
- 没有思路,因为不知道怎么将二进制字符串与编码头一一对应,并且如果用map在解码的时候需要通过值找键,而我对map掌握还不熟练,看了书上的代码发现可以不用二进制字符串对应,因为二进制字符串是有规律的:
1.1. 长度
1.2. 十进制的值 - 两个值表示一个字符,可以用二维数组
- 还有一点:编码头独占一行而输入又是连续的,因此要考虑读到换行符的可能性
- 还有读取方式的问题,一位一位读显然比一次性读取方便得多
AC代码
#include <bits/stdc++.h>
using namespace std;
/*不一定要用map将二进制的字符串与输入的编码头一一对应,而且这样处理很麻烦
正确来说根本不知道怎么一一对应
根据书上的思路,二进制字符串根据长度和十进制的值来区分,这意味着可以通过这个与
编码头对应 (两个变量表示一个值:二维数组)
*/
char code[10][1<<8];
char readb();
int readcode()
{//编码头独占一行,所以考虑换行符的处理
memset(code,0,sizeof(code));
char c = readb();
code[1][0] = c;//第一个编码头特殊处理
for(int i=2;i<8;i++)//i表示编码长度
{
for(int j=0;j<(1<<i)-1;j++)//j表示编码的值
{
// c = getchar();
// if(c=='\n'||c=='\r') return 0; 隐患:当多数据的时候,c读到换行符直接返回
//但此时编码还没读完
c = getchar();
if(c=='\n') return 1;//要进入循环处理
if(c==EOF) return 0;
code[i][j] = c;
}
}
}
char readb()
{
for(;;)
{
char ch = getchar();
if(ch=='\n'||ch=='\r') continue;
return ch;
}
}
int readint(int x)
{//因为是几位几位读取,有可能读到换行符
int ans = 0;
while(x--)
{
ans = ans*2+readb()-'0';
}
return ans;
}
int main()
{
/*一次性读取字符串赋值时不好处理*/
// freopen("in.txt","r",stdin);
while(readcode())
{
int len;
while((len=readint(3))!=0)
{
while(true){
int y = readint(len);
if(y==(1<<len)-1) break;
printf("%c",code[len][y]);
}
}
printf("\n");
}
}
总结:
- 当scanf或者getchar()读取到EOF时,再继续读取还是会读到EOF而不是自动停止
- 多组数据一定要清零全局变量
例题4-5(x)
Spreadsheet Tracking
走这
问题分析:
- 一开始的思路是根据操作更新电子表,但是错在只考虑了r和c的范围,并且没有考虑操作后单元格移动,即没有在每个操作更新坐标,比如删除2、3行,删除了第二行第三行就变成第二行,这是没有在代码中体现的
- 书上有两种思路,一是根据操作更新整个电子表,二是着眼于需要查看的单元格,对单元格进行操作
AC代码:(整体)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100;
int d[maxn][maxn],d2[maxn][maxn],ans[maxn][maxn],col[maxn];
int r,c,n;
void copy(char t,int x,int p)
{
//不能将赋值函数放在函数里,否则每次进入就重新赋值
if(t=='R')
for(int i=1;i<=c;i++)
d[p][i] = d2[x][i];
else{
for(int i=1;i<=r;i++)
d[i][p] = d2[i][x];
}
}
int main()
{
int kcase = 0;
while(scanf("%d%d%d",&r,&c,&n)==3&&r)
{
memset(d,0,sizeof(d));
memset(ans,0,sizeof(ans));
for(int i=1;i<=r;i++){
for(int j=1;j<=c;j++)/*保存原坐标*/
d[i][j] = maxn*i+j;//值/maxn得到行 %maxn得到列
}
char op[10];//char数组不能用==
int q,u;
while(n--)
{
scanf("%s",op);
if(op[0]=='E'){
int x,y,m,n;
scanf("%d%d%d%d",&x,&y,&m,&n);
swap(d[x][y],d[m][n]);//所有单元格都要带着原坐标移动
}else{
memset(col,0,sizeof(col));
if(op[0]=='D'){
memcpy(d2,d,sizeof(d));//赋值前需要让两个一模一样
int x,cnt= 0 ;
scanf("%d",&x);//需要标记删除哪些行,赋值时直接跳过删除的行
for(int i=0;i<x;i++)
{
int t;
scanf("%d",&t);col[t] = 1;
}
if(op[1]=='R')
{
for(int i=1;i<=r;i++)
if(!col[i]) copy('R',i,++cnt);//cnt起到了赋值和计数的作用
r = cnt;
}else{
for(int i=1;i<=c;i++)
if(!col[i]) copy('C',i,++cnt);//cnt起到了赋值和计数的作用
c = cnt;
}
}else{
int x,cnt= 0 ;
scanf("%d",&x);//需要标记插入哪些行
memcpy(d2,d,sizeof(d));//赋值前需要让两个一模一样
for(int i=0;i<x;i++)
{
int t;
scanf("%d",&t);col[t] = 1;
}
if(op[1]=='R')
{
for(int i=1;i<=r;i++)
{
if(col[i]) copy('R',0,++cnt);//cnt起到了赋值和计数的作用
copy('R',i,++cnt);
}
r = cnt;
}else{
for(int i=1;i<=c;i++)
{
if(col[i]) copy('C',0,++cnt);//cnt起到了赋值和计数的作用
copy('C',i,++cnt);
}
c = cnt;
}
}
}
} /*d数组是操作后,那么让ans数组的值存储d数组的行和列*/
for(int i=1;i<=r;i++)
{
for(int j=1;j<=c;j++)
{
ans[d[i][j]/maxn][d[i][j]%maxn] = i*maxn+j;//被删除的单元格不会出现在下标
}
}
if(kcase>0) printf("\n");
printf("Spreadsheet #%d\n",++kcase);
scanf("%d",&q);
while(q--)
{
int x1,y1; scanf("%d%d",&x1,&y1);
if(!ans[x1][y1]) printf("Cell data in (%d,%d) GONE\n",x1,y1);
else printf("Cell data in (%d,%d) moved to (%d,%d)\n",x1,y1,ans[x1][y1]/100,ans[x1][y1]%100);
}
}
}
代码分析:
- 因为查询的是原坐标,一开始觉得需要用结构体存储原坐标,看了书上代码才发现还有一种方式,借用一个常数,通过/和%获得原坐标
- 重要的是每次操作后都要更新单元格,因为有可能原单元格到r、c范围之外了
- 每次移动都要带着原坐标移动
- char数组是不能用==的
- 这里利用col的一维数组标记那些行列需要移动
- 操作是同时进行的,先进行坐标小的会影响大的坐标
单个操作:
#include <bits/stdc++.h>
using namespace std;
int const maxn = 1000;
int c,r,n;
struct cmp{
bool operator()(int x,int y)
{
return x>y;
}
};
struct com{
string ch;
int x,y,m,n;
int cnt;//命令的行列数
int col[20];
}com[maxn];
int cope(int& a,int& b)
{
for(int i=0;i<n;i++){
if(com[i].ch=="EX")
{
if(a==com[i].x&&b==com[i].y) {
a = com[i].m; b = com[i].n;
}else if(a==com[i].m&&b==com[i].n){
a = com[i].x;b=com[i].y;
}
}else{
for(int j=0;j<com[i].cnt;j++)
{
if(com[i].col[j]==a&&com[i].ch=="DR") return 0 ;//如果被删除后面就没必要执行
else if(com[i].col[j]==b&&com[i].ch=="DC") return 0;
else if(com[i].col[j]<a&&com[i].ch=="DR") a--;
else if(com[i].col[j]<b&&com[i].ch=="DC") b--;
else if(com[i].col[j]<=a&&com[i].ch=="IR") a++;
else if(com[i].col[j]<=b&&com[i].ch=="IC") b++;
}
}
}
return 1;
}
int main()
{
// freopen("in.txt","r",stdin);
int kcase = 0;
while(scanf("%d%d%d",&r,&c,&n)==3&&r)
{//对单个单元格进行模拟
if(kcase>0) printf("\n");
printf("Spreadsheet #%d\n",++kcase);
//必须先将命令储存
for(int i=0;i<n;i++)
{
cin>>com[i].ch;
if(com[i].ch=="EX") cin>>com[i].x>>com[i].y>>com[i].m>>com[i].n;
else {
cin>>com[i].cnt;
for(int j=0;j<com[i].cnt;j++) cin>>com[i].col[j];
}
sort(com[i].col,com[i].col+com[i].cnt,cmp());//这里的操作是同时操作的
}
int q;
cin>>q;
for(int i=0;i<q;i++){
int a,b;
cin>>a>>b;
printf("Cell data in (%d,%d) ",a,b);
if(!cope(a,b)) printf("GONE\n");
else printf("moved to (%d,%d)\n",a,b);
}
}
}
- 在函数中即时地更新了单元格坐标,但因为单元格操作是同时进行的,所以还是等一次操作完成后再更新较好
例题4-6
A Typical Homework (a.k.a Shi Xiong Bang Bang Mang)
问题分析:
- 易错点是没有即时更新存储sid的数组,这样会导致有本该进入的没有进入,统计每班的人数、各科总分都需要通过remove操作更新(考虑不周全)
- Binary_search()只适合查找有序的序列,在这使用是错误的
- 输入输出不要忘记。(卡了几个小时)
- 有时候没必要一次性更新全部,只需要看需要输出的即可,这样更方便比如 rank
- 除法记得考虑0,这里可能没有进行add操作
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 10010;
/*易错:1.除0 2.没有更新存储sid的数组 3.二分查找只适合有顺序的数组
4.输出不符合格式*/
struct ss{
string name;
string sid;
int cid;
int chinese,math,english,program,sum;
double avg;
int id;//表示输入顺序
};
vector<ss> v;
vector<string> sidx;
int banji[30],removed;
int sst = 0;//可能进行多次add
string sub[8] = {"","Chinese","Mathematics","English","Programming","Overall:"};
int ranks(ss st)
{
int cnt = 0;
for(int i=0;i<v.size();i++){
if(v[i].sum>st.sum)
{
cnt++;
}
}
return cnt+1;
}
void finds(string d,int type,bool q)//1表示姓名 q表示是否查询
{
removed = 0;
for(auto it = v.begin();it!=v.end();it++)
{
if(type){
if(it->name==d){
if(!q){
banji[it->cid]--;
banji[0]--;
auto its = find(sidx.begin(),sidx.end(),it->sid);
sidx.erase(its);
it = v.erase(it);it--;
removed++;
}else{
int r = ranks((*it));
printf("%d %s %d %s %d %d %d %d %d %.2lf\n",r,(it->sid).c_str(),it->cid,(it->name).c_str(),it->chinese,it->math,it->english,it->program,it->sum,it->avg);
}//如果不接收返回值,it就变成野指针
}
}else{
if(it->sid==d)
{
if(!q){
banji[it->cid]--;
banji[0]--;
auto its = find(sidx.begin(),sidx.end(),it->sid);//为什么不能
it = v.erase(it);it--;
sidx.erase(its);
removed++;
break;
}else{
int r = ranks(*it);
printf("%d %s %d %s %d %d %d %d %d %.2lf\n",r,(it->sid).c_str(),it->cid,(it->name).c_str(),it->chinese,it->math,it->english,it->program,it->sum,it->avg);
}
}
}
}
}
void add()
{
string id;
while(true)
{
printf("Please enter the SID, CID, name and four scores. Enter 0 to finish.\n");
cin>>id;
if(id=="0") break;
if(find(sidx.begin(),sidx.end(),id)!=sidx.end()){//二分查找只适合排好序的
string ss;
getline(cin,ss);
printf("Duplicated SID.\n");
continue;
}else{
sidx.push_back(id);
}
ss st;
st.sid = id;
cin>>st.cid>>st.name>>st.chinese>>st.math>>st.english>>st.program;
st.sum = st.chinese+st.math+st.english+st.program;
st.id = v.size();
st.avg = st.sum*1.0/4+1e-5;//???
banji[st.cid]++;
banji[0]++;
v.push_back(st);
}
}
void remove()
{
printf("Please enter SID or name. Enter 0 to finish.\n");
string d;
while(cin>>d&&d!="0")
{
if(isdigit(d[0])) finds(d,0,false);
else finds(d,1,false);
printf("%d student(s) removed.\n",removed);
printf("Please enter SID or name. Enter 0 to finish.\n");
}
}
void query()
{//可以只计算要查询的人的成绩 排名
printf("Please enter SID or name. Enter 0 to finish.\n");
string d;
while(cin>>d&&d!="0"){
if(isdigit(d[0])) finds(d,0,true);
else finds(d,1,true);
printf("Please enter SID or name. Enter 0 to finish.\n");
}
}
void show()
{
int x,score[10][10];
memset(score,0,sizeof(score));
printf("Please enter class ID, 0 for the whole statistics.\n");
scanf("%d",&x);
for(int i=0;i<v.size();i++)
{
if(v[i].cid==x||x==0)
{
ss st = v[i];
int counts = 0;
score[1][1] += st.chinese;score[2][1] += st.math;
score[3][1] +=st.english;score[4][1]+=st.program;
if(st.chinese>=60) {score[1][2]++; counts++;}//2表示通过人数
else score[1][3]++;
if(st.math>=60) {score[2][2]++; counts++;}
else score[2][3]++;
if(st.english>=60) {score[3][2]++; counts++;}
else score[3][3]++;
if(st.program>=60) {score[4][2]++; counts++;}
else score[4][3]++;
for(int i=counts;i>0;i--) score[0][i]++;//如果过了counts那么一定过了counts-1门
if(counts==0) score[0][0]++;
}
}
for(int i=1;i<=5;i++)
{
printf("%s\n",sub[i].c_str());
if(i<=4){
if(banji[x]==0)
{
printf("Average Score: %.2lf\n",0);
}else{
printf("Average Score: %.2lf\n",score[i][1]*1.0/banji[x]+1e-5);
}
printf("Number of passed students: %d\n",score[i][2]);
printf("Number of failed students: %d\n\n",score[i][3]);
}else{
printf("Number of students who passed all subjects: %d\n",score[0][4]);
printf("Number of students who passed 3 or more subjects: %d\n",score[0][3]);
printf("Number of students who passed 2 or more subjects: %d\n",score[0][2]);
printf("Number of students who passed 1 or more subjects: %d\n",score[0][1]);
printf("Number of students who failed all subjects: %d\n\n",score[0][0]);
}
}
}
int main()
{
freopen("in.txt","r",stdin);
freopen("out2.txt","w",stdout);
printf("Welcome to Student Performance Management System (SPMS).\n\n1 - Add\n2 - Remove\n3 - Query\n4 - Show ranking\n5 - Show Statistics\n0 - Exit\n\n");
int x;
while(scanf("%d",&x)&&x)
{
switch(x)
{
case 1: add();
break;
case 2: remove();
break;
case 3: query();
break;
case 4: printf("Showing the ranklist hurts students' self-esteem. Don't do that.\n");
break;
case 5: show();
break;
default:
break;
}
printf("Welcome to Student Performance Management System (SPMS).\n\n1 - Add\n2 - Remove\n3 - Query\n4 - Show ranking\n5 - Show Statistics\n0 - Exit\n\n");
}
}
习题
习题4-1
象棋
走这
这个单元的每个模拟题都卡了我超久,要不就是哪里思路不周全,要不就是输入输出有问题
问题分析:
- 这道题的思路就是模拟黑方将帅,类似走迷宫将各个位置检查一遍,如果合法就输出NO
- 本题我被坑到的点:
- 当模拟将帅走时,有可能出现吃掉红方子的情况(因为不一定只被一个子将军)
- 当红子可以吃掉黑子时,要注意前面是否有子
- 双重循环里不要用相同变量
- 复制功能差不多的代码要简单修改下,因为不可能一模一样
- 当黑子发现此步死局需要回溯时,需要检测该步原来有没有子,有子就不需要修改mp数组
- 全局变量不要忘了清零
AC代码:
#include <bits/stdc++.h>
using namespace std;
int n,mp[15][15],wx,wy;
int xx[4] = {-1,1,0,0};
int yy[4] = {0,0,-1,1};
struct chess{
char power;
int x,y;
}chess[10];
bool check(int x,int y)//x与y表示黑将的坐标
{//易错:每个棋子走的地方前面可能有子
for(int i=1;i<=n;i++){
if((chess[i].x==x&&chess[i].y==y))
{
continue;//易错 如果能吃掉对方
}
if(chess[i].power=='G')
{
if(y==chess[i].y)
{//如果走了的黑将和红将在同一列
bool flag = true;
for(int j=x+1;j<chess[i].x;j++)//易错点:不要用相同变量!!!
{
if(mp[j][y]) {flag = false; break;}
}
if(flag) return false;
}
}
else if(chess[i].power=='R')
{
if(y==chess[i].y||x==chess[i].x)
{
bool flag = true;
if(y==chess[i].y)
{
for(int p=min(x,chess[i].x)+1;p<max(x,chess[i].x);p++){
if(mp[p][y]) {
flag = false; break;
}
}
if(flag) return false;
}else{
for(int p=min(y,chess[i].y)+1;p<max(y,chess[i].y);p++){
if(mp[x][p]) {//易错三:复制时没修
flag = false; break;
}
}
if(flag) return false;
}
}
}
else if(chess[i].power=='C')
{
if(chess[i].y==y&&chess[i].x<x){
int cnt = 0;
for(int p=chess[i].x+1;p<x;p++){
if(mp[p][y]){
cnt++;
}
}
if(cnt==1) return false;
}
if(chess[i].y==y&&chess[i].x>x){
int cnt = 0;
for(int p=x+1;p<chess[i].x;p++){
if(mp[p][y]){
cnt++;
}
}
if(cnt==1) return false;
}
if(chess[i].x==x&&chess[i].y<y){
int cnt = 0;
for(int p=chess[i].y+1;p<y;p++){
if(mp[x][p]){
cnt++;
}
}
if(cnt==1) return false;
}
if(chess[i].x==x&&chess[i].y>y){
int cnt = 0;
for(int p=y+1;p<chess[i].y;p++){
if(mp[x][p]){
cnt++;
}
}
if(cnt==1) return false;
}
}
else if(chess[i].power=='H'){
if(chess[i].x-2==x&&(y==chess[i].y-1||y==chess[i].y+1)&&!mp[chess[i].x-1][chess[i].y] )
return false;
if(chess[i].x+2==x&&(y==chess[i].y-1||y==chess[i].y+1)&&!mp[chess[i].x+1][chess[i].y] )
return false;
if(chess[i].y-2==y&&(x==chess[i].x-1||x==chess[i].x+1)&&!mp[chess[i].x][chess[i].y-1])
return false;
if(chess[i].y+2==y&&(x==chess[i].x-1||x==chess[i].x+1)&&!mp[chess[i].x][chess[i].y+1])
return false;
}
}
return true;
}
void move(int x,int y)
{
for(int i=0;i<4;i++){
int dx = x+xx[i];
int dy = y+yy[i];
bool flag = true;
if(dx>0&&dx<4&&dy>3&&dy<7){
if(mp[dx][dy] == 1)//易错:棋局上原本有子的地方回溯就不能改为无子
flag = false;
else mp[dx][dy] = 1;
if(check(dx,dy))
{
printf("NO\n");
return;
}
if(flag)
mp[dx][dy] = 0;
}
}
printf("YES\n");
}
int main()
{
// freopen("in.txt","r",stdin);
// freopen("out2.txt","w",stdout);
int bx,by,r = 0;
while(cin>>n>>bx>>by&&n)
{
// printf("%d %d %d\n",n,bx,by);
memset(mp,0,sizeof(mp));//易错点:全局清零
// char c = getchar();
for(int i=1;i<=n;i++)//审题,要输入完
{
cin>>chess[i].power>>chess[i].x>>chess[i].y;
mp[chess[i].x][chess[i].y] = 1;//c = getchar();
if(chess[i].power=='G'){
wx = chess[i].x;
wy = chess[i].y;
}
// printf("%c %d %d\n",chess[i].power,chess[i].x,chess[i].y);
}
//printf("%d",++r);
move(bx,by);
// c = getchar();
}
}
写的很繁琐,车和炮的操作应该能并在一起,因为都是一次走一行
习题4-2 正方形
走这
问题分析:
- 思路是dfs…尝试写了之后发现写不出来,然后换个思路,枚举边长,然后看哪些点在此正方形上,但这样会有边明明不存在,但是两边端点是存在的,就会多算了正方形
- 看了题解发现是枚举正方形的左上角坐标与边长,通过记录边的起始坐标记录边,这样就可以独特地记录边,还能避免重复
- 还有一个点是正方形只需要外部的边,内部是否有边不需要统计
AC代码:
#include <bits/stdc++.h>
using namespace std;
int n,m;//枚举边长,再枚举正方形的左上角的坐标
int row[110][110]; //二维数组表示边,因为方向固定,所以起始坐标处 = 1
int col[110][110];
bool check(int x,int y,int a)
{//易错:不用判断内部的线,只需要判断外部的线
//bool flag = true,flag2 = true;
for(int i=x;i<x+a;i++)
{
if(!col[i][y]) return false;
if(!col[i][y+a]) return false;
}
for(int i=y;i<y+a;i++){
if(!row[x][i]) return false;
if(!row[a+x][i]) return false;
}
return true;
}
int main()
{
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
int kcase = 0;
while(cin>>n>>m){
bool sq = true;
if(kcase) printf("\n**********************************\n\n");
printf("Problem #%d\n\n",++kcase);
memset(row,0,sizeof(row));
memset(col,0,sizeof(col));
for(int i=1;i<=m;i++){
char c;int x,y;
cin>>c>>x>>y;
if(c=='H'){
row[x][y] = 1;
}else{
col[y][x] = 1;
}
}
for(int i=1;i<=n;i++){
int cnt = 0;
for(int x=1;x<n;x++){
for(int y=1;y<n;y++){
if(x+i<=n&&y+i<=n&&check(x,y,i)){
cnt++;
sq = false;
}
}
}
if(!sq&&cnt>0) printf("%d square (s) of size %d\n",cnt,i);
}
if(sq) printf("No completed squares can be found.\n");
}
}
习题4-3
黑白棋
走这
也是卡了我超久的题目…大部分都是些小细节,还有令人吐血的输入输出
问题分析:
- 首先我们需要看懂题目,L是看有没有能走的位置,如果能走就走,不能走就换人继续,M下棋,下完棋后就是另外一个人下棋,因此要换玩家身份
- 思路就是正常地模拟,通过对子坐标来放己方棋子,需要设立一个方向数组,因为是两个人轮流放子,所以我用到了%,输出坐标时用multimap存储坐标
- 说说我在这题犯的错误:
- . 每次进行L操作时,都需要将上一次的容器清空,否则会导致错误,清空代码放的位置也很重要
- 检查是否能放子的时候,放子位置与目标黑子之间不能相隔空格
- 反复提醒:erase函数不会修改容器位置,可以精准去掉目标迭代器
- 假设位置合法,我们需要检查它与对子之间是否只有对子,而不是其他的子,只能是对子
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100;
char mp[10][10],owner[2];
bool used[10][10];
int xx[10] = {-1,0,-1,-1,1,0,1,1};// (i+4)%8回到相反方向
int yy[10] = {0,-1,-1,1,0,1,1,-1};
vector<int> vb;//存储黑子坐标
vector<int> vw;
vector<vector<int> > v;
vector<pair<int,int> > mx;
struct cmp{
bool operator()(const pair<int,int>& a,const pair<int,int>& b){
if(a.first!=b.first) return a.first<b.first;
else return a.second<b.second;
}
};
void print()
{
for(int i=1;i<=8;i++){
for(int j=1;j<=8;j++){
cout<<mp[i][j];
}
cout<<endl;
}
}
bool check(char p,int type,vector<int>& v,int counts)//0表M 1表L
{
mx.clear();//每次判断能不能下棋的时候 ,需要将上次下的位置清空
memset(used,0,sizeof(used));
int kcase = 0;bool flag = false;
for(int i=0;i<v.size();i++)
{
int bx = v[i]/100; int by = v[i]%100;
for(int j=0;j<8;j++)
{
int t = 1,dx = bx,dy = by,ft = 1,fx = bx,fy = by;//步长
while(dx<9&&dy<9&&dx>0&&dy>0)//放子 dx dy = '-'
{
dx = bx+xx[j]*t; dy = by+yy[j]*t;
t++;
if(mp[dx][dy]==owner[(counts+1)%2]) continue;//如果是对手的棋子,则继续
else if(mp[dx][dy]==p) break;//如果是己方棋子,就说明这个方向不能再放
else if(mp[dx][dy]=='-') break;
// if(mp[dx][dy]==p) break;
// if(dx<9&&dx>0&&dy<9&&dy>0){
// if(used[dx][dy]&&mp[dx][dy]=='-') break;
// else if(used[dx][dy]&&mp[dx][dy]!='-') continue;
// if(mp[dx][dy]=='-'&&!used[dx][dy]){//易错点,没有检查放的位置是否相隔空格
// break;
// }
// }
// if(dx>8||dx<1||dy>8||dy<0) break;
}
if(mp[dx][dy]=='-'&&!used[dx][dy]){//假如位置合法
while((mp[fx][fy]==owner[(counts+1)%2])&&fx<9&&fx>0&&fy<9&&fy>0){//找子 fx fy = p
fx = dx+ xx[(j+4)%8]*ft;//中间不能有'-'和己方棋子
fy = dy+ yy[(j+4)%8]*ft;
ft++;
//if(mp[fx][fy]=='-')
}
if(mp[fx][fy]==p){
if(type){
// printf("此时是%c %d %d\n",p,bx,by);
// printf("第%d个方向\n",j);
// printf("%d %d\n",dx,dy);
mx.push_back(make_pair(dx,dy));
flag = true;
used[dx][dy] = true;//修改表示被放过了
}else{
return true;
}
}
}
}
}
sort(mx.begin(),mx.end(),cmp());
if(flag&&type){
for(auto it = mx.begin();it!=mx.end();it++){
if(kcase) printf(" ");
printf("(%d,%d)",it->first,it->second);
kcase++;
}
}
if(!flag&&type) printf("No legal move.\n");
else if(type) printf("\n");
return false;
}
void place(char p,string com,int& cntb,int& cntw,int counts)
{
char c;
int x = com[1]-'0'; int y = com[2]-'0'; int cnt = 0;
// printf("%c 放置%d %d\n",p,x,y);
if(p=='W') {c='B'; v[(counts+1)%2].push_back(x*100+y); }
else {c = 'W'; v[(counts+1)%2].push_back(x*100+y);}
for(int i=0;i<8;i++){
int dx,dy,t = 1;
do{
dx = x+xx[i]*t;
dy = y+yy[i]*t;
t++;
}while(mp[dx][dy]==c);
if(mp[dx][dy]!=p) continue;
else{
if(t<=2)
{
continue;
}
t = 1;
while(mp[x+xx[i]*t][y+yy[i]*t]!=p)
{//因为要两个人轮流所以用数组比较方便
cnt++;
dx = x+xx[i]*t; dy = y+yy[i]*t;
mp[dx][dy] = p;
v[(counts+1)%2].push_back(dx*100+dy);
auto it = find(v[(counts)%2].begin(),v[(counts)%2].end(),dx*100+dy);
v[(counts)%2].erase(it);
t++;
}
}
}
mp[x][y] = p;
if(p=='W') { cntb-=cnt; cntw+=cnt;cntw++;}//易错:下棋需要加1
else { cntw-=cnt; cntb+=cnt;cntb++;}
printf("Black -%3d White -%3d\n",cntb,cntw);//不是相间两个空格,而是%3d
// print();
}
int main()
{//理解题目:选手1用L 看是否有子可走,有就走,没有就换人走,每下完一步就换人走
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
int n;
cin>>n;
for(int k=0;k<n;k++){
if(k) cout<<endl;
memset(mp,0,sizeof(mp));
v.clear();vb.clear();vw.clear();
int cntw = 0; int cntb = 0;int counts = 0;
for(int i=1;i<=8;i++)
{
for(int j=1;j<=8;j++)
{
cin>>mp[i][j];//避免换行符
if(mp[i][j]=='B') { cntb++;vb.push_back(i*maxn+j); }
if(mp[i][j]=='W') { cntw++;vw.push_back(i*maxn+j);}
}
}
//printf("%d\n%d\n",vw.size(),vb.size());
char player;string com;
cin>>player;
owner[0] = player;
if(player=='W') { owner[1] = 'B'; v.push_back(vb); v.push_back(vw) ; }
else if(player=='B') { owner[1] = 'W'; v.push_back(vw); v.push_back(vb) ; }
while(com[0]!='Q'){
cin>>com;
switch(com[0])
{
case 'Q': print();
break;
case 'L': //经过M操作后,棋子的坐标改变了
//不能在这里清零,因为M还要检查
// printf("下棋手是%c\n",owner[counts%2]);
check(owner[counts%2],1,v[counts%2],counts);
break;
case 'M':
if(check(owner[counts%2],0,v[counts%2],counts))
{
// printf("下棋手是%c\n",owner[counts%2]);
place(owner[counts%2],com,cntb,cntw,counts);
}else{
counts++;
// printf("下棋手是%c\n",owner[counts%2]);
place(owner[counts%2],com,cntb,cntw,counts);
}
counts++;
break;
default:
break;
}
}
}
}
习题4-4 骰子涂色
走这
思路:
- 一开始我认为是找规律,结果找了半天找不出来,看了大佬的代码 原来是选定一轴然后模拟骰子换面
问题分析:
- 此题就是模拟骰子旋转,将需要旋转的面互换值即可,一共有三个轴,每个4次旋转,一共是444=64种,范围很小,可以直接枚举
- 注意:不能利用三个对面是否对应规则相同检查,因为有反例:aaaaaa 与gggggg(但是我看其他大佬的代码,貌似能利用这个点过,srds我不知道怎么写)
AC代码:
#include <bits/stdc++.h>
using namespace std;
void row1(string& tmp){//模拟右转
char a = tmp[1]; char b = tmp[3]; char c = tmp[4]; char d = tmp[2];
tmp[1] = d; tmp[3] = a; tmp[4] = b; tmp[2] = c;
}
void row2(string& tmp){//每次都转4个面,4次回归原位
char a = tmp[0]; char b = tmp[1]; char c = tmp[5]; char d = tmp[4];
tmp[0] = d; tmp[1] = a; tmp[5] = b; tmp[4] = c;
}
void row3(string& tmp){
char a = tmp[0]; char b = tmp[3]; char c = tmp[2]; char d = tmp[5];
tmp[2] = d; tmp[3] = a; tmp[5] = b; tmp[0] = c;
}
int main()
{
// freopen("in.txt","r",stdin);
string s;
while(getline(cin,s))//回车停止接收
{
string a = s.substr(0,6); string b = s.substr(6);
bool flag = true;
for(int i=0;i<4;i++){
row1(a);
for(int j=0;j<4;j++){
row2(a);
for(int k=0;k<4;k++){
row3(a);
if(a==b){
flag = false;
break;
}
}
if(!flag) break;
}
if(!flag) break;
}
if(!flag) printf("TRUE\n");
else printf("FALSE\n");
}
}
习题4-5 Morse Mismatches
走这
思路:
- 用两个map容器,一个是单个字母对应的莫斯码,另一个是摩斯码与对应的单词,但是做到后面发现单词的摩斯码是可以一样的,所以考虑multimap,但是看了评论区有更好的解法,用嵌套容器map与vector存储摩斯码对应单词,符合摩斯码的直接放入内层容器vector中(即每个单词对应一个容器),最后匹配摩斯码
问题分析:
- 难点主要在想到map<string, vector >的构建,只要想到这个就能很轻松的存储对应多个单词
- 还有一个是前缀和后缀的判断,我自己判断前缀的方法是erase,后缀想用dfs,看了大佬的代码发现压根不用这么复杂,如果a是b的后缀,那么b就是a的前缀,利用此法就可解,并且判断前缀也不需要erase,而是substr
AC代码:
#include <bits/stdc++.h>
using namespace std;
map<char,string> m;
map<string,vector<string> >sm;//因为有可能几个单词拼接对应摩斯码相同,所以最好不要Map容器存储 单词
int main()
{
// freopen("in.txt","r",stdin);
char c;string s,a;
while(cin>>c&&c!='*'){
cin>>s;
m.insert(make_pair(c,s));
}
while(cin>>a&&a[0]!='*'){
string tmp;
for(int i=0;i<a.size();i++){
tmp+=(m[a[i]]);
}
sm[tmp].push_back(a);//将摩斯码对应的单词放入vector中
}
while(cin>>a&&a[0]!='*'){
if(sm.find(a)!=sm.end()){//如果存在
cout<<*sm[a].begin();
if(sm[a].size()>1) cout<<"!";
cout<<endl;
}else{//不存在
int lens = 0xfffffff;string d;
/*找a是字母对应摩斯码前缀的或者a是后缀的(思路是dfs,但有更好的方法)*/
for(auto it = sm.begin();it!=sm.end();it++){
if(a==it->first.substr(0,a.size())||it->first==a.substr(0,(it->first).size())){
/*左是前缀,右是后缀,本质都是互相对比是不是前缀*/
/*不能找到就输出,要找相差最少的*/
int len = abs((long)a.size()-(long)(it->first).size());
if(lens>len){
lens = len;
d = *((it->second).begin());
}
}
}
cout<<d<<"?"<<endl;
}
}
}
总结:
- 一对多的关系可以考虑map嵌套容器
- C++11中abs有多种类型,我们需要确定一个类型否则会OJ会报错
- 多用逆向思维,这道题的前缀后缀用逆向思维很容易解
习题4-9 数据挖掘
走这
本题看懂题目就很费劲,看别人代码敲的,留着不写以后再补
习题4-10 洪水
走这
思路:
- 很容易想到就是一个个加直到下次加超出范围,这道题和数列分段有点像,看了评论区还真能用二分做,srds我不会,因此用的就是普通方法
AC代码:
#include <bits/stdc++.h>
using namespace std;
int n,m,water;
const double ebp = 1e-5;
int nums[1000];
vector<int> v;
void flood(){
int sum = 0,i = 0;
for(i=0;i<v.size();i++)
{
if((sum+v[i]*(i+1))<water) sum+=v[i]*(i+1);
else break;
}//易错1: 有sum ==0的情况,因为网格太高,但是 水平面高度是不为0的
double h = 1.0*(water-sum)/100/(i+1)+nums[i+1];
int cnt = i+1; double hud = cnt*1.0/(n*m);
printf("Water level is %.2lf meters.\n",h);
printf("%.2lf percent of the region is under water.\n\n",hud*100);
}//错点:题目并没有要求最后一个不输出空行,所以if(kcase)是多此一举
int main()
{
freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int kcase = 0;
while(cin>>n>>m&&n&&m)
{
printf("Region %d\n",++kcase);
v.clear();
fill(nums,nums+1000,0);
for(int i=1;i<=n*m;i++){
cin>>nums[i];
}
sort(nums+1,nums+n*m+1);
for(int i=2;i<=n*m;i++){
v.push_back((nums[i]-nums[i-1])*100);
}
cin>>water;
flood();
}
}
总结:
- 这道题和浮点误差感觉没多大关系,普通地做就能过
- 输出格式不要自作聪明少输出一个空行!
总总结:
- 这章感觉就是各种模拟题,有些涉及位运算的还没写以后待补
- 这些题最坑人的不是各种细节,而是输入输出的方式,一定要看题选好方式
- 细节方面就是要考虑周全一点,全局变量一定要重置