问题 A: [蓝桥杯2019初赛]求和
思路:直接暴力,循环的时候判断当前访问的数,是否符合要求,符合就把它算进结果即可。
#include<bits/stdc++.h>
using namespace std;
int check(int k)
{
while(k)
{
int t=k%10;
if(t==2||t==0||t==1||t==9) return 1;
k/=10;
}
return 0;
}
int main()
{
int sum=0;
for(int i=1;i<=2019;i++)
{
if(check(i)) sum += i;
}
printf("%d",sum);
}
问题 B: [蓝桥杯2015初赛]星系炸弹
思路:n不超过1000,所以也是暴力即可,将循环天数,设定年月日为y,m,d,从d开始累加,满了往前进位即可,注意区分下平年闰年的二月即可。
#include<bits/stdc++.h>
using namespace std;
int check(int y)
{
if((y%4==0&&y%100!=0)||(y%400==0)) return 1;
else return 0;
}
int mon[13][2]={{0,0},{31,31},{28,29},{31,31},{30,30},{31,31},{30,30},{31,31},{31,31},{30,30},{31,31},{30,30},{31,31}};
int main()
{
int y,m,d,t;
while(~scanf("%d%d%d%d",&y,&m,&d,&t))
{
for(int i=1;i<=t;i++)
{
d++;
int p=check(y);
if(d>mon[m][p]) d=1,m++;
if(m>12) m=1,y++;
}
printf("%04d-%02d-%02d\n",y,m,d);
}
}
问题 C: 查找与给定值最接近的元素
思路:题目给的是一个有序序列,那么我们直接去序列中二分查找给定元素的位置即可,准确来说是去找大于等于给定元素的位置,如果找到的位置恰好就是给定元素,那么这个元素肯定是最近的一个,如果不相等,那么找到的位置的元素大于给定元素,找到位置的左边的元素是所有小于给定元素的元素中最大的,答案就在这两个中产生。
#include<bits/stdc++.h>
using namespace std;
int a[200010];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int m;
scanf("%d",&m);
while(m--)
{
int x;
scanf("%d",&x);
int l=1,r=n;
while(l<r)
{
int mid=(l+r)/2;
if(a[mid]>=x) r=mid;
else l=mid+1;
}
int y=a[l-1],z=a[l];
if(z-x<x-y) printf("%d\n",z);
else printf("%d\n",y);
}
}
问题 D: 二分解方程
思路:我们可以判一下单调性,这个等式左边是单调递增的,所以这也是个二分题,不过和上面的整数二分略有不同。 因为浮点数有误差的,所以我们判断是否相等的时候要给一个误差eps=1e-8。另外对于二分出来的结果也要判断一下是否合法,这里如果不合法相差就大了,所以这时候判断误差不用把精度设的太高,否则会误判。
#include<bits/stdc++.h>
using namespace std;
const double eps=1e-8;
double y;
int check(double mid)
{
double ans=8.0*mid*mid*mid*mid+7.0*mid*mid*mid+2.0*mid*mid+3.0*mid+6.0;
if(ans-y>=eps) return 1;
else return 0;
}
int cmp(double mid)
{
double ans=8.0*mid*mid*mid*mid+7.0*mid*mid*mid+2.0*mid*mid+3.0*mid+6.0;
if(fabs(ans-y)<=1e-1) return 1;
else return 0;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%lf",&y);
double l=0,r=100;
while(r-l>eps)
{
double mid=(l+r)*1.0/2;
if(check(mid)) r=mid;
else l=mid;
}
if(cmp(l)) printf("%.4lf\n",l);
else printf("No solution!\n");
}
}
问题 E: 单词的长度
思路:实际上直接暴力,访问到非空格字符去再开一个循环往后找到当前单词的结尾即可。不过有一点需要特别注意,这里字符串有空格,所以用fgets()来输入,但是fgets会把"\n",所有样例中只有第一个样例结尾有\n,剩下的字串结尾都没有\n,所以需要特判一下最后一个字符是否是\n。
#include<bits/stdc++.h>
using namespace std;
int main()
{
char s[2000];
fgets(s,2000,stdin);
vector<int>q;
int len=strlen(s);
if(s[len-1]=='\n') len--;
for(int i=0;i<len;i++)
{
if(s[i]!=' ')
{
int d=i;
int c=0;
while(d<len&&s[d]!=' ')
{
c++;
d++;
}
q.push_back(c);
i=d;
}
}
for(int i=0;i<q.size();i++)
{
if(i) printf(",");
printf("%d",q[i]);
}
}
问题 F: 加密的病历单
思路:因为这三个操作是对整个字串进行操作的,所以它们的顺序实际没有影响。我们依次还原即可。
#include<bits/stdc++.h>
using namespace std;
int main()
{
string s;
cin>>s;
//大小写还原
for(int i=0;i<s.size();i++)
{
if('A'<=s[i]&&s[i]<='Z')s[i]+=32;
else s[i]-=32;
}
reverse(s.begin(),s.end());//逆转
for(int i=0;i<s.size();i++)//移动还原
{
if('a'<=s[i]&&s[i]<='z') s[i]=(s[i]-'a'+3)%26+'a';
else s[i]=(s[i]-'A'+3)%26+'A';
}
cout<<s;
}
问题 G: [蓝桥杯2015初赛]三羊献瑞
思路:这题看似很麻烦,但实际上是暴力题,因为每一个字符能代表的数就在0-9之间,那么我们写个多层嵌套的循环就能找到。不过有两点需要注意,不同字符代表的数不能相同,以及“三”和“祥”代表的数不能是0。
#include<bits/stdc++.h>
using namespace std;
int main()
{
//三羊献瑞祥生辉气
//a,b,c,d,e,f,g,h
int flag=0;
for(int a=1;a<=9;a++)
{
for(int b=0;b<=9;b++)
{
if(b==a) continue;
for(int c=0;c<=9;c++)
{
if(c==b||c==a) continue;
for(int d=0;d<=9;d++)
{
if(d==a||d==b||d==c) continue;
for(int e=1;e<=9;e++)
{
if(e==a||e==b||e==c||e==d) continue;
for(int f=0;f<=9;f++)
{
if(f==a||f==b||f==c||f==d||f==e) continue;
for(int g=0;g<=9;g++)
{
if(g==a||g==b||g==c||g==d||g==e||g==f) continue;
for(int h=0;h<=9;h++)
{
if(h==a||h==b||h==c||h==d||h==e||h==f||h==g) continue;
int x=a*1000+b*100+c*10+d;
int y=e*1000+d*100+f*10+g;
int z=a*10000+b*1000+f*100+d*10+h;
if(x+y==z)
{
flag=1;
printf("%d%d%d%d",a,b,c,d);
break;
}
}
if(flag) break;
}
if(flag) break;
}
if(flag) break;
}
if(flag) break;
}
if(flag) break;
}
if(flag) break;
}
if(flag) break;
}
}
问题 H: [蓝桥杯2015初赛]生命之树
思路:这题说要在树上找点集,使得点集中的点可以互达,同时点集中的数的和最大。我最开始看到a,b以为是找树的直径,直接就树形dp了,但是仔细看了才发现这里是要找点集。不过我们可以按照树形dp的思路来分析一下:
我们一这个图为例来看,如果我们想把5和7选进点集,那么2一定要被选。如果我们要找以2为根的点集,那么2肯定要被包含,5如果为正值,那么就可以被算进点集,6,7同理。如果我们要找以1为根的点集,那么肯定也是从下面三棵子树中去找,如果子树的和为正,那么就可以被选入点集,如果子树的和为负,那么就不用选。这里和树形dp找树的直径就有点像了,不过比那个简单一点。这里我们只要是正的子树都可以被算进结果,那么我们只要递归查找就可以找到以每个数为根的点集的最大和,遍历找出最大值即可。
#include<bits/stdc++.h>
using namespace std;
int a[100010],h[100010],e[200010],ne[200010],dp[100010],idx;
int n;
void add(int x,int y)
{
e[idx]=y,ne[idx]=h[x],h[x]=idx++;
}
int dfs(int u,int fa)
{
int d=0;
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(j==fa) continue;
int t=dfs(j,u);
if(t>0) d += t;
}
dp[u]=max(dp[u],d+a[u]);
return d+a[u];
}
int main()
{
memset(h,-1,sizeof h);
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<n;i++)
{
int b,c;
scanf("%d%d",&b,&c);
add(b,c);
add(c,b);
}
dfs(1,-1);
int mx=0;
for(int i=1;i<=n;i++) mx=max(mx,dp[i]);
cout<<mx;
}
ps:这块儿写的有点抽象,我贴一个树形dp的博客,大家参考一下(树形dp模型整理-CSDN博客)特别是博客中的第一个题,可以辅助理解这道题的dp过程。
问题 I: Dongdziz与不说阿拉伯语的阿拉伯商人
思路:这道题显然就是在1e9中找到满足要求的最大的数,那么直接二分即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int A,B,x;
int check(int mid)
{
int ans=A*mid;
int c=0;
while(mid)
{
c++;
mid/=10;
}
ans += B*c;
if(ans<=x) return 1;
else return 0;
}
signed main()
{
scanf("%lld%lld%lld",&A,&B,&x);
int l=0,r=1e9;
while(l<r)
{
int mid=(l+r+1)/2;
if(check(mid)) l=mid;
else r=mid-1;
}
cout<<l;
}
问题 J: 回文游戏
思路:这个只有修改一个操作,那么就很简单了,直接双指针遍历,然后对于不同的修改一个即可。
#include<bits/stdc++.h>
using namespace std;
int main()
{
string s;
cin>>s;
int c=0;
for(int i=0,j=s.size()-1;i<j;i++,j--)
{
if(s[i]!=s[j]) c++;
}
printf("%d",c);
}
问题 K: 2.4.2 Web导航
思路:这道题实际上就是模拟题,定义两个栈,按照要求一步一步操作就好。有一点要注意的就是,操作如果被忽略,那么当前页面是不变的。
#include<bits/stdc++.h>
using namespace std;
int main()
{
stack<string>b,q;
string op,s="***###.acm.org/";
while(cin>>op)
{
if(op=="VISIT")
{
b.push(s);
cin>>s;
cout<<s<<endl;
while(q.size()) q.pop();
}
else if(op=="BACK")
{
if(b.size())
{
q.push(s);
s=b.top();
b.pop();
cout<<s<<endl;
}
else cout<<"Ignored"<<endl;
}
else if(op=="FORWARD")
{
if(q.size())
{
b.push(s);
s=q.top();
q.pop();
cout<<s<<endl;
}
else cout<<"Ignored"<<endl;
}
else
{
break;
}
}
}
问题 L: 2.4.3 骑士移动
思路:这里要判断一个点能否到另一个点,在每一个点处它有八种移动的情况,所以想到搜索来解决,又因为这里求的是最小步数,那么直接bfs就可。
#include<bits/stdc++.h>
using namespace std;
int ans=0;
int n,a,b;
int dx[]={1,1,-1,-1,2,-2,2,-2};
int dy[]={2,-2,2,-2,1,1,-1,-1};
int st[400][400];
struct node
{
int x,y,v;
};
void bfs(int x,int y)
{
st[x][y]=1;
queue<node>q;
q.push({x,y,0});
while(q.size())
{
auto it=q.front();
int x=it.x,y=it.y,v=it.v;
q.pop();
if(x==a&&y==b)
{
ans=v;
break;
}
for(int i=0;i<8;i++)
{
int nx=x+dx[i],ny=y+dy[i];
if(0<=nx&&nx<n&&0<=ny&&ny<n&&!st[nx][ny])
{
st[nx][ny]=1;
q.push({nx,ny,v+1});
}
}
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
int x,y;
scanf("%d%d%d%d",&x,&y,&a,&b);
ans=0x3f3f3f3f;
memset(st,0,sizeof st);
bfs(x,y);
cout<<ans<<endl;
}
}
问题 M: 大食堂
思路:这道题吧,我想了半天都觉得是贪心题,分析出来了一定要队伍最长的和手速最快的阿姨配对,然后就是怎么选择k个同学,这里就卡住了,我一直在找合适的排序,甚至都用上优先队列了,但是显然优先队列去模拟的话会超时。贪心实在贪不明白了,跑去问了黄大佬,大佬说这是个二分题,二分时间判断是否合法就可以了。然后我一写,果然ac了。就是很显然这个时间是有个范围的从0到一个都不删的最大时间,那么就是去这个区间中找最小值,我们可以二分查找,然后判断需要选择的同学的数量是否超过k,进而判断这个时间是否合法,然后就解决了。所以还是得多练练呀!
#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[200010],f[200010];
struct node
{
int p,s;
}c[200010];
int n,k,sum=0;
int check(int mid)
{
int d=0;
for(int i=1;i<=n;i++)
{
int p=c[i].p,s=c[i].s;
int ti=p*s;
if(ti>mid)
{
int del=(ti-mid)/s;
if(ti-del*s>mid) del++;
d += del;
}
if(d>k) return 0;
}
return 1;
}
signed main()
{
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]),sum+=a[i];
for(int i=1;i<=n;i++) scanf("%lld",&f[i]);
if(k>=sum) printf("0\n");
else
{
sort(a+1,a+1+n);
sort(f+1,f+1+n);
int mx=0;
for(int i=1;i<=n;i++)
{
c[i]={a[i],f[n-i+1]};
mx=max(mx,a[i]*f[n-i+1]);
}
int l=0,r=mx;
while(l<r)
{
int mid=(l+r)/2;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l;
}
}
问题 N: 吃菜
思路:这道题很明显是贪心题,那么就要找一个合适的排序,因为时间有限值,那么就想到按照时间从小到大排序,然后就贪不下去了,因为点菜是不花时间的,我可以在最后一秒点一个时间花费特别大但是美味度也特别高的菜,这个菜显然不一定刚好在我们当前已经点过的菜后面一个,所以遍历没办法解决。这里每个菜只能点一次,时间有限值,每个菜有一个耗费时间和一个美味度,那么很容易和01背包联系起来。这样就有思路了,我们还是按照时间从小到大排序,然后我们预处理出来每一个菜后面价值最大的菜的价值。然后在背包容量为t-1的情况下做一遍01背包
定义dp[i][j]为从前i个菜中选,消耗时间不超过j的价值集合,这个值表示最大价值。
状态转移则为:
不选第i个菜:dp[i][j]=dp[i-1][j]
选第i个菜:dp[i][j]=dp[i-1][j-a]+b
dp[i][j]则为两者的最大值。
然后我们开始遍历菜品,从dp[i][t-1]+mx[i]中找最大值。mx[i]表示i后面价值最大的菜品是哪个。然后问题就解决了。
#include<bits/stdc++.h>
using namespace std;
struct node
{
int a,b;
}c[200010];
bool cmp(node x,node y)
{
return x.a<y.a;
}
int dp[3010][3010];
int mx[3010];
int main()
{
int n,t;
scanf("%d%d",&n,&t);
for(int i=1;i<=n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
c[i]={x,y};
}
sort(c+1,c+1+n,cmp);
mx[n]=0;
for(int i=n-1;i>=1;i--)
{
mx[i]=max(mx[i+1],c[i+1].b);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=t-1;j++)
{
dp[i][j]=dp[i-1][j];
if(c[i].a<=j)dp[i][j]=max(dp[i][j],dp[i-1][j-c[i].a]+c[i].b);
}
}
int ans=0;
for(int i=1;i<=n;i++)
{
ans=max(ans,dp[i][t-1]+mx[i]);
}
cout<<ans;
}
问题 O: 矩形
思路:如下图,显然给出红圈之后,可以将整个区域的绿色菱形都画出来,那么就是要看哪些哪些红圈可以像这样画在一个区域中。
显然它们的横纵坐标之间有关系,但是还是很麻烦,这里借鉴了曾大佬之前的博客中的一个实现方法——并查集。(曾大佬的博客),这么来想:我们如果将所有点的纵坐标视为横坐标的父节点,那么上图中就化简成了三个连通块:
然后这三个连通块实际是一个区域中的,那么我们就要将它们也联系起来:
这样,整块区域中所有的点的父节点都统一成了r,那么我们如果去遍历统计所有横坐标的父节点,就可以知道在r区域内有多少个x,也即有多少列,遍历纵坐标的父节点,就可以知道在r区间内有多少个y,也即有多少行。区间内总的点数应该是列数*行数,然后再减去已知的点,就是我们想要的结果。我们对每个区间都这么处理即可。另外为了区分横纵坐标,因为它们都是数,可以借用曾大佬的写法,将y散列到M+1-2*M的区间中去,因为我们在处理并查集的时候就要将它们区分,不然并查集就乱了。然后这道题就可以解决了。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+10,M=1e5+10;
int p[N];
int find(int u)
{
if(p[u]!=u) p[u]=find(p[u]);
return p[u];
}
int c1[N],c2[N];
signed main()
{
int n;
scanf("%lld",&n);
for(int i=1;i<N;i++) p[i]=i;
for(int i=1;i<=n;i++)
{
int x,y;
scanf("%lld%lld",&x,&y);
p[find(x)]=find(y+M);
}
for(int i=1;i<=M;i++) c1[find(i)]++;
for(int i=M+1;i<=2*M;i++) c2[find(i)]++;
//printf("1\n");
int sum=0;
for(int i=1;i<=2*M;i++) sum += c1[i]*c2[i];
printf("%lld",sum-n);
}