鉴于本人将参加第12届蓝桥杯,所以从网上找来第11届真题做,发现自己还有许多不足之处,就记录一下。
思路: 编写一个函数,将一个数的各位数字分离,计算有多少个2
代码:
#include <cstdio>
#include <algorithm>
using namespace std;
int numoftwo(int n) {
int m=0;
while(n>0) {
int t=n%10;
if(t==2) {
m++;
}
n/=10;
}
return m;
}
int main() {
int a=2020,sum=0;
for(int i=1; i<=a; i++) {
int k=numoftwo(i);
if(k!=0) {
printf("%d %d\n",i,k);
}
sum+=k;
}
printf("%d\n",sum);
return 0;
}
思路: 暴力枚举不解释
代码:
#include <cstdio>
#include <algorithm>
using namespace std;
int gcd(int x,int y){
int t=x%y;
if(t==0)
return y;
gcd(y,t);
}
int main(){
int i,j,cnt=0;
for(i=1;i<2021;i++){
for(j=1;j<2021;j++){
if(gcd(i,j)==1){
// printf("%d/%d ",i,j);
cnt++;
}
}
}
printf("%d\n",cnt);
return 0;
}
思路: 设行号,列号分别为i,j(均从0开始),矩阵用二维数组a保存。观察这个矩阵,不难看出:
1.a[0][0]=1;
2.对于每行首列元素,若i为偶数,则a[i][0]=a[i-1][0]+1,若i为奇数,则a[i][0]=a[i-1][0]+k,其中k初值为2,每次递增4;
3.对于其他元素,若i+j为奇数,则a[i][j]=a[i][j-1]+2*i-1,若为偶数,则a[i][j]=a[i][j-1]+m,当i为奇数时,m初值为2,否则初值为4,每次递增4,注意每行开始时m都要刷新。
代码:
#include <cstdio>
#include <algorithm>
#define N 30
using namespace std;
bool iseven(int x) {
return x%2==0?true:false;
}
int main() {
int i,j,m,k=2; //m:列递增器,k:行递增器
int a[N][N];
a[0][0]=1;
for(i=0;i<N;i++){
if(iseven(i))
m=4;
else
m=2;
for(j=0;j<N;j++){
if(j!=0){
if(!iseven(i+j))
a[i][j]=a[i][j-1]+(2*i+1);
else {
a[i][j]=a[i][j-1]+m;
m+=4;
}
}
else if(i!=0){ //计算每行首列元素,注意首行不能算进去
if(iseven(i))
a[i][0]=a[i-1][0]+1;
else{
a[i][0]=a[i-1][0]+k;
k+=4;
}
}
}
}
/* for(i=0;i<21;i++){
for(j=0;j<21;j++)
printf("%5d",a[i][j]);
printf("\n");
}*/
printf("%d\n",a[19][19]);
return 0;
}
思路: 发现两个公式:
1.总里程数=天数+周一数+月份数-既是周一又是每月1日的天数;
2.周一数=第一天之后第一个周一到最后一天之间的天数(包括这两天)/7+1。
接下来思路就比较顺其自然了,比较麻烦的是各种边界条件的处理。
天数用Windows自带的计算器也可算出,但注意要+1。
代码:
#include <cstdio>
#include <algorithm>
using namespace std;
int dpm[13]= {0,31,28,31,30,31,30,31,31,30,31,30,31};
bool isleap(int y) {
if(y%4==0) {
if(y%400==0)
return true;
if(y%100==0&&y%400!=0)
return false;
} else
return false;
}
int days(int s_year,int e_year,int e_month,int e_d) { //计算天数,返回s_year年元旦到e_year年e_month月e_d日的天数
int date=0,m,d;
for(int i=s_year; i<e_year; i++) {
if(isleap(i)) {
dpm[2]=29;
date+=366;
} else {
dpm[2]=28;
date+=365;
}
}
m=d=1; //最后一年
if(isleap(e_year))
dpm[2]=29;
else
dpm[2]=28;
for(m=1; m<e_month; m++)
date+=dpm[m];
date+=e_d;
return date;
}
int main() {
int a=2000,b=2020,m=1,d=1,hr,nom,mth;//b:最终年份,a,m,d:当前日期,nom:星期一数,hr:总小时数,mth:总月份数
int k=6; //k为当前星期数
nom=(days(a,b,10,1)-2)/7+1;
mth=12*(b-a)+9+1;
hr=days(a,b,10)+mth+nom;
while(a<b) { //这个只能对2000-2019去重
if(isleap(a))
dpm[2]=29;
else
dpm[2]=28;
while(m<13) {
while(d<=dpm[m]) {
if(k==1&&d==1) {
// printf("%d.%d.%d 星期%d\n",a,m,d,k);
hr--; //去重
}
d++;
if(d<=dpm[m])
k++;
if(k==8)
k=1; //返回星期一
}
m++;
if(m<13) { //防止重算
d=1;
k++;
if(k==8)
k=1;
}
}
a++;
m=d=1;
k++;
if(k==8)
k=1;
}
printf("%d\n",hr-1); //2020.6.1也是星期一
return 0;
}
不会做……自己硬数数出来79,但答案应该是80。看网上说要什么并查集,但本人并没有学过这个,看来还有待提升啊。
思路: 暴力,不多说,注意百分号的输出。
代码:
#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;
int main() {
int a[10000],n,i,good=0,pass=0;
double jgl,yxl;
scanf("%d",&n);
for(i=0; i<n; i++)
scanf("%d",&a[i]);
for(i=0; i<n; i++){
if(a[i]>=60)
pass++;
if(a[i]>=85)
good++;
}
jgl=(pass/(double)n)*100;
yxl=(good/(double)n)*100;
printf("%.0lf%c\n",jgl,'%');
printf("%.0lf%c\n",yxl,'%');
return 0;
}
思路: 同样是暴力,注意循环每执行一次就要把年月日提取出来放到数组里。用f1,f2标记两种类型的输出次数,均为1时代表输出完毕,终止循环。
有两个问题:
1.输出的“回文日期”是否应排除ABABBABA型(我认为不用);
2.11111111是否为ABABBABA型“回文日期”(我认为是的);
代码:
#include <cstdio>
#include <algorithm>
using namespace std;
int dpm[13]= {0,31,28,31,30,31,30,31,31,30,31,30,31};
int year[4],month[2],day[2],f1=0,f2=0; //f1,f2标志两种类型日期输出次数
bool isleap(int y) {
if(y%4==0) {
if(y%400==0||y%100!=0)
return true;
else if(y%100==0&&y%400!=0)
return false;
}
return false;
}
void fillin(int y,int m,int d) { //填充数组
//每加一天调用一次
int i=3;
while(y>0) {
year[i]=y%10;
y/=10;
i--;
}
month[0]=m/10;
month[1]=m%10;
day[0]=d/10;
day[1]=d%10;
}
bool hw(void) { //判断是否回文
if(year[0]==day[1]&&year[1]==day[0]&&year[2]==month[1]&&year[3]==month[0])
return true;
return false;
}
bool hw_special(void) { //判断是否为ABABBABA型
if(year[0]==year[2]&&year[1]==year[3]&&hw())
return true;
return false;
}
void print(void) {
if(hw()&&f1==0) {
for(int i=0; i<4; i++)
printf("%d",year[i]);
printf("%d%d%d%d\n",month[0],month[1],day[0],day[1]);
f1++;
}
if(hw_special()) {
for(int i=0; i<4; i++)
printf("%d",year[i]);
printf("%d%d%d%d\n",month[0],month[1],day[0],day[1]);
f1++;
f2++;
}
}
int main() {
int date,cy,cm,cd;
scanf("%d",&date);
fill(year,year+4,0);
fill(month,month+2,0);
fill(day,day+2,0);
cy=date/1e4;
cd=date%100;
cm=date/100-cy*100;
fillin(cy,cm,cd);
//printf("%d.%d.%d\n",cy,cm,cd);
while(f1<=1&&f2<=1) {
if(isleap(cy))
dpm[2]=29;
else
dpm[2]=28;
while(cm<13) {
while(cd<dpm[cm]) {
cd++;
fillin(cy,cm,cd);
print();
}
cm++;
cd=1;
fillin(cy,cm,cd);
print();
}
cy++;
cm=cd=1;
fillin(cy,cm,cd);
print(); //为防止每月第一天的情况被遗漏,此处和84行都要调用print
}
return 0;
}
总结:对于日期相关的题目,在第三、第二层循环结束后要考虑是否执行第三层循环里面的操作。
思路: 一开始想用DFS,后来看了样例说明,发现直接暴力也可以,但会超时,只能过40%数据。
代码:
#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAX 100000
using namespace std;
int flag[100][100];
int fenzhi(char *s,int b,int e) {
int cnt=0,i,j;
for(i=b; i<e; i++) {
for(j=b; j<i; j++) {
if(s[j]==s[i])
break;
}
if(j>=i)
cnt++;
}
return cnt;
}
int main() {
char c[MAX];
long long int sum=0;
gets(c);
for(int i=0; i<=strlen(c); i++) {
for(int j=i+1; j<=strlen(c); j++) {
// printf("%d %d %d\n",i,j,fenzhi(c,i,j));
sum+=fenzhi(c,i,j);
}
}
printf("%I64d",sum);
return 0;
}
会的就这么多。
2021.2.8更新
关于H题,想到一种更加高效的办法,可以过50%数据
那就是计算f值时,用一个数组标记某个字母是否出现过,如果未出现,则当前f值加1,并将该字母标记为出现过。
代码如下:
int fenzhi(char *s,int b,int e) {
bool vis[26]; //判断每个字母是否出现过
int cnt=0,i;
memset(vis,false,sizeof(vis));
for(i=b; i<e; i++) {
if(!vis[s[i]-'a'])
cnt++;
vis[s[i]-'a']=true;
}
return cnt;
}
总结一下,判断一个有限集合(如本题为小写字母集合)内某个元素是否出现过时,可设置标记数组。
再如:提取一系列非负整数(不大于某一特定值N)中仅出现1次的数,可设置长度为N+1的标记数组,只需一层循环,而不必使用两层。
更新
发现了一种新的做法:
用一个数组保存以每个字符开头的子串的f值,最后将它们相加即可
注意一下代码只能保存以i开头以最后一个字母结尾的子串的f值,所以cnt的位置应格外注意
可以通过60%数据,极限数据量约为29000
代码:
#include <cstdio>
#include <algorithm>
#include <string>
#include <iostream>
#define MAX_LEN 100001
using namespace std;
long long fz[MAX_LEN]; //记录以i起始至字符串结尾子串的f值
bool vis[27]; //记录每个字符的首次出现的位置
int main(){
long long cnt=0;
string s;
cin>>s;
int len=s.length();
cnt=len; //只有一个字符时
fill(fz,fz+len,1);
for(int i=0;i<len;i++){ //起始字符
fill(vis,vis+27,0);
vis[s[i]-'a']=true;
for(int j=i+1;j<len;j++){ //结尾字符
if(!vis[s[j]-'a']){
fz[i]++;
vis[s[j]-'a']=true;
}
cnt+=fz[i]; //这个要放在if外部
}
}
printf("%lld",cnt);
return 0;
}