蓝桥杯赛前整理
离比赛还不到一周,刷题停了,回顾下之前经常遇到的点
比赛里可以用万能头文件:#include <bits/stdc++.h>
由于比赛用的是Windows评测机,输出longlong时建议用%i64d
1、模运算
关于模运算的几个公式:
第4个公式,如果b较大时,可以采用费马小定理化简
比如上周日的一道模拟题:
可以先用费马小定理对n进行化简,令n=n%100,最后利用快速幂就能算出答案
费马小定理:a^(p-1)%p=1 条件:p为质数并且a<p
代码:
#include <bits/stdc++.h>
using namespace std;
const int mod=101;
int fast_pow(int a,int b,int p)//快速幂取模
{
int r=1%p;
a%=p;
while(b)
{
if(b&1)
{
r=(r*a)%p;
}
b>>=1;
a=(a*a)%p;
}
return r;
}
int main()
{
int n=1;
int sum=0;
for(int i=1;i<=2019;i++)
{
n=n*2019%100;//不断将n进行化简
}
for(int i=1;i<=11;i++)
{
sum+=fast_pow(i,n,mod);
}
cout<<sum%mod<<endl;
return 0;
}
2、最大公约数
代码实现:
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
PS:也可以使用c++库里的__gcd()函数,但是要注意前面是两条下划线
3、拆数问题
#include <bits/stdc++.h>
using namespace std;
const int n=20192019;
int ans;
int main()
{
for(int i=1;i<=n;i++)
{
int x=i;
while(x)
{
if(x%10==7)
{
ans++;
}
x/=10;
}
}
printf("%d\n",ans);
return 0;
}
4、判断是否为素数以及筛法求素数
注意:1不是素数!
判断是否为素数:
bool is_PrimNum(int n)
{
if(n==1)
{
return false;
}
if(n==2)
{
return true;
}
for(int i=2;i*i<=n;i++)
{
if(n%i==0)
{
return false;
}
}
return true;
}
筛法求素数,以下面这道题为例:
AC代码:
#include <bits/stdc++.h>
using namespace std;
bool p[1000005];
int main()
{
int m;
scanf("%d", &m);
//预处理 假设都为质数
for (int i = 2; i <= 1000000; i++)
{
p[i] = true;
}
//筛法求素数 欧式筛
for (int i = 2; i * i <= 1000000; i++)
{
if (p[i])
{
for (int j = 2; i * j <= 1000000; j++)
{
p[i*j] = false;
}
}
}
while (m--)
{
int n;
scanf("%d", &n);
for (int i = 2;i < n; i++)
{
if (p[i] && p[n - i])
{
printf("%d %d\n", i, n - i);
break;
}
}
}
return 0;
}
5、日期问题
注意判断是否为闰年,一般日期问题就是对翻日历过程的模拟
以高斯的日记为例:
AC代码:
#include <bits/stdc++.h>
using namespace std;
int is_leap_year(int y)//闰年判断
{
if(y%100!=0&&y%4==0||y%400==0)
{
return 1;
}
else
{
return 0;
}
}
int main()
{
int y=1777;
int m=4;
int d=30;
int k;
cin>>k;
int day[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
//模拟翻日历过程
for(int i=2;i<=k;i++)//由于出生那天就算一天 所以循环从2开始
{
if(is_leap_year(y))
{
day[2]=29;
}
else
{
day[2]=28;
}
d++;
if(d>day[m])
{
d=1;
m++;
}
if(m>12)
{
m=1;
y++;
}
}
printf("%04d-%02d-%02d",y,m,d);
return 0;
}
6、二分
①两个二分函数:
upper_bound();//找出大于目标元素的第一个元素
lower_bound();//找出等于或者大于目标元素的第一个元素
②两个二分模板:
//求最大值最小 区间为[l,mid] [mid+1,r]
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid))
{
r=mid;
}
else
{
l=mid+1;
}
}
//求最小值最大 区间为[l,mid-1] [mid,r]
while(l<r)
{
int mid=(l+r+1)>>1;
if(check(mid))
{
l=mid;
}
else
{
r=mid-1;
}
}
7、DFS和BFS
①数独问题:(DFS)
AC代码:
#include <bits/stdc++.h>
using namespace std;
char s[15][15];
bool f;
bool vx[10][10];
bool vy[10][10];
bool vv[10][10];
void dfs(int x,int y)
{
if(f)
{
return;
}
if(x==9)
{
f=true;
for(int i=0;i<9;i++)
{
for(int j=0;j<9;j++)
{
if(j!=8)//判断行末空格
{
printf("%c ",s[i][j]);
}
else
{
printf("%c\n",s[i][j]);
}
}
}
return ;
}
if(y==9)
{
dfs(x+1,0);
return ;
}
if(s[x][y]!='*')
{
dfs(x,y+1);
return ;
}
for(int i=1;i<=9;i++)
{
if(!vx[x][i]&&!vy[y][i]&&!vv[x/3*3+y/3][i])
{
s[x][y]='0'+i;
vx[x][i]=true;
vy[y][i]=true;
vv[x/3*3+y/3][i]=true;
dfs(x,y+1);
vx[x][i]=false;
vy[y][i]=false;
vv[x/3*3+y/3][i]=false;
s[x][y]='*';
}
}
}
int main()
{
for(int i=0;i<9;i++)
{
for(int j=0;j<9;j++)
{
scanf(" %c",&s[i][j]);
}
}
for(int i=0;i<9;i++)
{
for(int j=0;j<9;j++)
{
if(s[i][j]!='*')
{
vx[i][s[i][j]-'0']=true;
vy[j][s[i][j]-'0']=true;
vv[i/3*3+j/3][s[i][j]-'0']=true;
}
}
}
dfs(0,0);
return 0;
}
②密码锁(BFS)
BFS通常与结构体一起使用,并且在结构体中,要注意构造函数,还要注意BFS的要在入队之前标记
#include <bits/stdc++.h>
using namespace std;
bool vis[11][11][11][11];//分别标记每一位
struct node
{
int num[4];
int step;
}first,last;
queue<node> q;
void bfs()
{
node a,next;
a=first;
a.step=0;
q.push(a);
memset(vis,0,sizeof(vis));
vis[a.num[0]][a.num[1]][a.num[2]][a.num[3]]=true;
while(!q.empty())
{
a=q.front();
q.pop();
if(a.num[0]==last.num[0]&&a.num[1]==last.num[1]&&a.num[2]==last.num[2]&&a.num[3]==last.num[3])
{
printf("%d\n",a.step);
return ;
}
for(int i=0;i<4;i++)
{
next=a;
next.num[i]++;
if(next.num[i]==10)
{
next.num[i]=1;
}
if(!vis[next.num[0]][next.num[1]][next.num[2]][next.num[3]])
{
vis[next.num[0]][next.num[1]][next.num[2]][next.num[3]]=true;
next.step++;
q.push(next);
}
}
for(int i=0;i<4;i++)
{
next=a;
next.num[i]--;
if(next.num[i]==0)
{
next.num[i]=9;
}
if(!vis[next.num[0]][next.num[1]][next.num[2]][next.num[3]])
{
vis[next.num[0]][next.num[1]][next.num[2]][next.num[3]]=true;
next.step++;
q.push(next);
}
}
for(int i=0;i<3;i++)
{
next=a;
swap(next.num[i+1],next.num[i]);
if(!vis[next.num[0]][next.num[1]][next.num[2]][next.num[3]])
{
vis[next.num[0]][next.num[1]][next.num[2]][next.num[3]]=true;
next.step++;
q.push(next);
}
}
}
}
int main()
{
char s1[10];
char s2[10];
scanf("%s%s",s1,s2);
for(int i=0;i<4;i++)
{
first.num[i]=s1[i]-'0';
last.num[i]=s2[i]-'0';
}
bfs();
return 0;
}
注意:当数据范围较大或者求最值问题时,选用BFS,当要求输出所有情况时,选用DFS。
8、vector、map、set
①当要求数组较大时,利用vector,但是要注意二维vector的使用,这里将二维vector比喻成书包和钱包,也就是说,当书包里有钱包时,我们才能往钱包里面放钱,钱包就相当于一维的vector,而书包就相当于二维的vector。vector常用于构造一张无向图。
②map常用于记录某个元素出现了多少次
③set常用于去重排序
注意:map和set的遍历需要迭代器(iterator)。三个容器的头元素都是begin,尾元素为end
9、几个常用的函数
①sort();//排序函数 默认从小到大排列 也可以增加一个函数让它从大到小排列
②find();//string类中的查找函数 找到返回下标 没找到返回nops
③fgets();//不会读入空格 但是会读入回车 一般读一个数组时使用 一般写成:fgets(s,n-1,stdin);//n为字符数组的个数
④next_permutation();//全排列函数 配合do—while使用
⑤getline();//读入一个string 当遇到回车时结束 一般写成:getline(cin,str);//str为string类型
10、01背包
#include <bits/stdc++.h>
using namespace std;
int dp[1010];
int w[21],c[21];
int main()
{
int N,V;
cin>>N>>V;
for(int i=1;i<=N;i++)
{
cin>>w[i]>>c[i];
}
for(int i=1;i<=N;i++)
{
for(int j=V;j>=c[i];j--)
{
dp[j]=max(dp[j-c[i]]+w[i],dp[j]);
}
}
cout<<dp[V]<<endl;
return 0;
}
蒜头君回家:(注意边界情况的判断)
#include <iostream>
#include <algorithm>
using namespace std;
int a[110][110];
int dp[110][110];
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
cin >> a[i][j];
}
}
dp[1][1]=0;//一开始时 体力为0
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(i==1&&j==1)
{
continue;
}
else if(i==1)//当为第一行时 只能从第一列转移过来
{
dp[i][j]=dp[i][j-1]+a[i][j];
}
else if(j==1)//当为第一列时 只能从第一行转移过来
{
dp[i][j]=dp[i-1][j]+a[i][j];
}
else
{
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+a[i][j];
}
}
}
cout<<dp[n][n]<<endl;
return 0;
}
11、动态规划的几个模型
①最长上升子序列
//子序列 可以是不连续的
#include <bits/stdc++.h>
using namespace std;
const int N=100000;
int dp[N];
int n;
int a[N];
int ans;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
dp[i]=1;//初始化时为1 当没有时 自己就是一个子序列
for(int j=1;j<=i;j++)
{
if(a[j]<a[i])
{
dp[i]=max(dp[i],dp[j]+1);//把第i项接到第j项后面
}
}
ans=max(ans,dp[i]);//可以用ans保存最大值 也可以循环一遍dp数组求出最大值
}
cout<<ans<<endl;
return 0;
}
②最长公共子序列
#include <iostream>
#include <cstring>
#include <string>
using namespace std;
int dp[110][110];
int main()
{
string a,b;
memset(dp,0,sizeof(dp));
cin>>a>>b;
int lena=a.size();
int lenb=b.size();
for(int i=1;i<=lena;i++)
{
for(int j=1;j<=lenb;j++)
{
if(a[i-1]==b[j-1])
{
dp[i][j]=dp[i-1][j-1]+1;
}
else
{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
cout<<dp[lena][lenb]<<endl;
return 0;
}
③最大子段和
#include <bits/stdc++.h>
using namespace std;
const int N=10005;
int sum;
int ans=-100000;
int a[N];
int n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
ans=max(ans,a[i]);
}
if(ans<=0)
{
cout<<ans<<endl;
}
else
{
for(int i=1;i<=n;i++)
{
if(sum+a[i]<0)
{
sum=0;
}
else
{
sum+=a[i];
}
ans=max(ans,sum);
}
//ans=max(ans,sum);
}
//ans=max(ans,sum);
cout<<ans<<endl;
return 0;
}
④爬楼梯
#include <bits/stdc++.h>
using namespace std;
int n;
const int mod=100007;
long long dp[200005];
long long ans;
int main()
{
scanf("%d",&n);
dp[0]=dp[1]=1;
for(int i=2;i<=n;i++)
{
dp[i]=(dp[i-1]+dp[i-2])%mod;
}
printf("%d",dp[n]);
return 0;
}
12、树状数组
假设a[n]为原始数组,c[n]为树状数组
核心:low_bit函数
int low_bit(int x)
{
return x&(-x);
}
树状数组初始化操作:
void init()
{
for(int i=1;i<=n;i++)
{
c[i]+=a[i];
if(i+low_bit(i)<=n)
{
c[i+low_bit(i)]+=c[i];
}
}
}
求某个点的前缀和:
int sum(int x)
{
int r=0;
while(x)
{
r+=c[x];
x-=low_bit(x);
}
return r;
}
修改某个点的值:
void add(int x,int v)
{
while(x<=n)
{
c[x]+=v;
x+=low_bit(x);
}
}
emmmmm最后只希望能水个省三…