【蓝桥杯复习计划】C++程序设计B组
引用2020年官网的试题考查范围( 标*的部分不在B组考察范围 )。
计算机算法:枚举、排序、搜索、计数、贪心、动态规划、图论、数论、博弈论*、概率论*、计算几何*、字符串算法等。
数据结构:数组、对象/结构、字符串、队列、栈、树、图、堆、平衡树/线段树、复杂数据结构*、嵌套数据结构*等。
本篇博客主要是复习已经学过的基础算法,整理出一个板子来背一下。想学算法可以去博客里搜相应算法的名称。
【更新】20年的国赛没考什么算法,dfs暴搜和一个简单dp,细心一点就国一了
目录
1 搜索
1.1 bfs
I - Fire Game FZU - 2150(多起点BFS)
M - 非常可乐 HDU - 1495(BFS模拟题意,倒水问题)
What a Ridiculous Election ( 带约束条件的BFS )
#include<bits/stdc++.h>
using namespace std;
struct node {
int x,y,step;
}t,d;
int nxt[4][2] = {0,1,0,-1,1,0,-1,0};
int via[1005][1005];
int bfs( int x, int y )
{
queue<node> Q;
t.x = x; t.y = y; t.step=0;
Q.push(t);via[t.x][t.y]=1;
while ( !Q.empty() ) {
t = Q.top(); Q.pop();
if ( t.x & t.y 符合条件 ) return step;
for ( int i=0; i<4; i++ ) {
下一步的情况...
d.x = t.x+nxt[i][0];
d.y = t.y+nxt[i][0];
d.step = t.step+1;
if ( d.x & d.y 越界 ) continue ;
if ( via[d.x][d.y]==0 ) { // 之前没出现过的情况
Q.push(d);
via[d.x][d.y] = 1;
}
}
}
return -1;
}
int main()
{
return 0;
}
1.2 dfs
K - Birthday Puzzle ( DFS遍历数组元素的所有组合情况 )
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[22];
int n;
ll ans = 0;
void dfs( int node, int presum ) // 当前点,之前点的值
{
if ( node==n+1 ) {
return ;
}
ll temp = presum|a[node];
ans += temp;
bfs(node+1,temp); // 当前点参与运算
bfs(node+1,presum); // 当前点没有参加运算
}
int main()
{
int i;
cin >> n;
for ( i=1; i<=n; i++ ) cin >> a[i];
bfs(1,0);
cout << ans << endl;
return 0;
}
2 图论
2.1 最短路
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn = 2e5+10;
int head[maxn],cnt;
int dis[maxn],via[maxn];
struct edge {
int to,w,nxt;
}e[maxn*3];
struct node {
int v, dis;
}t,d;
bool operator<( const node &a, const node &b )
{
return a.dis>b.dis;
}
void add( int u, int v, int w )
{
e[cnt].to = v;
e[cnt].w = w;
e[cnt].nxt = head[u];
head[u] = cnt++;
}
void dij( int s )
{
memset(dis,inf,sizeof(dis));
priority_queue<node> Q;
t.v = s; t.dis=0;
Q.push(t);
dis[s] = 0; /// 重点
while ( !Q.empty() ) {
if ( via[Q.top().v]==1 ) {
Q.pop();
continue ;
}
t = Q.top(); Q.pop();
int u = t.v;
dis[u] = t.dis;
via[u] = 1;
for ( int i=head[u]; i!=-1; i=e[i].nxt ) {
int v=e[i].to, w=e[i].w;
if ( via[v]==0 && dis[v]>dis[u]+w ) {
dis[v] = dis[u]+w;
d.v = v;
d.dis = dis[v];
Q.push(d);
}
}
}
}
int main()
{
memset(head,-1,sizeof(head)); cnt=0;
return 0;
}
2.2 最小生成树
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+10;
struct node {
int from,to,w;
}e[maxn];
int n,k,m,ans;
int pre[maxn];
int rule( node a, node b )
{
return a.w<b.w;
}
int root( int x )
{
if ( x!=pre[x] ) {
pre[x] = root(pre[x]);
}
return pre[x];
}
void join( int a, int b, int w )
{
int x = root(a);
int y = root(b);
if ( x!=y ) {
pre[x] = y;
k ++;
ans += w;
}
}
int main()
{
cin>>n>>m;
for ( int i=1; i<=n; i++ ) pre[i]=i;
for ( int i=0; i<m; i++ ) {
cin>>e[i].from>>e[i].to>>e[i].w;
}
sort(e,e+m,rule);
k = 0;ans=0;
for ( int i=0; i<m; i++ ) {
join( e[i].from, e[i].to, e[i].w );
if ( k==n-1 ) break;
}
cout << ans << endl;
return 0;
}
2.3 拓扑排序
void topu()
{
queue<int> Q; // 队列只存入度为0的点
for ( int i=1; i<=n; i++ ) {
if ( in[i]==0 ) Q.push(i);
}
vector<int> ans; // 存拓扑序列
while ( !Q.empty() ) {
int t = Q.front(); Q.pop(); // 选一个入度为0的点出来
ans.push_back(t);
for ( int i=head[t]; i!=-1; i=e[i].nxt ) {
int to = e[i].to;
in[to]--; // 相连点度数--
if ( in[to]==0 ) Q.push(to);
}
}
return 0;
}
2.4 欧拉图
2.5 强连通分量*
2.6 二分图染色
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 2e5+10;
vector<int> G[maxn];
int via[maxn]; //标记是否染色,一开始为-1
int isp,n,m;
void dfs( int u )
{
for ( int i=0; i<G[u].size(); i++ ) {
int v = G[u][i];
if ( via[v]==-1 ) {
via[v] = via[u]^1; //没染色则染成相反的
dfs(v); //继续深搜
}
else if ( via[v]==via[u] ) { //如果发现不是二分图说明存在奇圈
isp = 0;
}
}
}
signed main()
{
memset(via,-1,sizeof(via)); isp=1;
cin>>n>>m;
for ( int i=0; i<m; i++ ) {
int u,v;cin>>u>>v;
G[u].push_back(v);
G[v].push_back(u);
}
via[1] = 0;// 起点染成0颜色
dfs(1);
return 0;
}
2.7 网络流*
3 树论
3.1 dfs序
#include<bits/stdc++.h>
#define int long long
using namespace std;
void dfs( int u, int f )
{
dfn[u] = ++tim;
rev[tim] = u;
sz[u] = 1;
for ( int i=head[u]; i!=-1; i=e[i].nxt ) {
int v = e[i].to;
if ( v==f ) continue ;
dfs(v,u);
sz[u] += sz[v];
}
}
signed main()
{
/*
那么对于u节点
他的所有子节点的范围就是
[ dfn[u], dfn[u] + sz[u] - 1 ]
*/
return 0;
}
3.2 树的直径
#include<bits/stdc++.h>
#define int long long
using namespace std;
int cir[2]; // left=cir[0], right=cir[1]
void dfs( int u, int f, int dep, int op ) /// 找最远点
{
if ( dep>now ) {
now = dep;
cir[op] = u;
}
for ( int i=head[u]; i!=-1; i=e[i].nxt ) {
int v = e[i].to;
if ( v==f ) continue ;
dfs(v,u,dep+1);
}
}
void dfs2( int u, int f ) /// 求最短路径,等价于dijstra
{
dep[u] = max(dep[u],cnt);
for ( int i=head[u]; i!=-1; i=e[i].nxt ) {
int v = e[i].to;
if ( v==f ) continue ;
dfs(v,u,dep+1);
}
}
signed main()
{
/*
树中所有最短路径距离的最大值即为树的直径。
方法:先从任意一点P出发,找离它最远的点Q,再从点Q出发,
找离它最远的点W,W到Q的距离就是是的直径。( 记住就好了 )
*/
now = 0;
dfs(1,1,0,0);
left=cir[0];
now = 0;
dfs(left,left,0,1);
right=cir[1]
cout << left << " " << right << endl; /// 直径的左右两个点
dfs2(left,left);
cout << dep[right] << endl; /// 直径
return 0;
}
3.3 倍增LCA
#include <bits/stdc++.h>
using namespace std;
struct node {
int to,nxt;
}e[2000005];
int head[500005],cnt,n,m,root;
int f[500005][22];
int dep[500005];
void addage( int u, int v )
{
e[cnt].to = v;
e[cnt].nxt = head[u];
head[u] = cnt++;
}
void dfs( int u, int fa ) // 对应深搜预处理f数组
{
dep[u] = dep[fa] + 1;
for ( int i=1; (1<<i)<=dep[u]; i++ ) {
f[u][i] = f[f[u][i-1]][i-1]; // 定义,往上走8步可以分为,先走4步再走4步
}
for ( int i=head[u]; i!=-1; i=e[i].nxt ) {
int v = e[i].to;
if ( v==fa ) continue; //双向图需要判断是不是父亲节点
f[v][0] = u;
dfs(v,u);
}
}
int lca( int x, int y )
{
if ( dep[x]<dep[y] ) swap(x,y);
for ( int i=20; i>=0; i-- ) {
if ( dep[ f[x][i] ] >= dep[y] ) x=f[x][i];
if ( x==y ) return x;
}
for ( int i=20; i>=0; i-- ) {
if ( f[x][i]!=f[y][i] ) { //尽可能接近
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
int main()
{
memset(head,-1,sizeof(head));cnt=0;
cin >> n >> m >> root;
for ( int i=0; i<n-1; i++ ) {
int u,v;scanf("%d %d",&u,&v);
addage(u,v); addage(v,u);
}
dfs(root,root);
for ( int i=0; i<m; i++ ) {
int a,b;scanf("%d %d",&a,&b);
printf("%d\n",lca(a,b));
}
return 0;
}
4 数论
4.1 欧拉筛
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+10;
int primes[maxn],cnt;
bool isprime[maxn+100];
void get_prime()
{
memset(isprime,true,sizeof(isprime));
for ( int i=2; i<maxn; i++ ) {
if ( isprime[i]==true ) primes[cnt++]=i;
for ( int j=0; j<cnt&&i*primes[j]<maxn; j++ ) {
isprime[ i*primes[j] ] = false;
if ( i%primes[j]==0 ) break; /// 这里是==0
}
}
}
int main()
{
get_prime();
cout << cnt << endl;
for ( int i=0; i<100; i++ ) cout << primes[i] << endl;
return 0;
}
4.2 除法分块
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
ll solve( ll n ) // 求1~n的所有因子和
{
ll sum=0;
ll left,right;
for ( left=1; left<=n; left=right+1 ) { // i这个因子有多少个
right = n/(n/left); // 右边界
sum += (n/left)*(right-left+1)*(left+right)/2;
}
return sum;
}
/*
因子 1 2 3 4 5 6 7 8 9 10 11 12
出现次数( n/i ) 12 6 4 3 2 2 1 1 1 1 1 1
*/
int main()
{
ll l,r;
cin >> l >> r;
cout << solve(r)-solve(l-1) << endl;
return 0;
}
4.3 扩展欧几里得
#include <bits/stdc++.h>
#define int long long
using namespace std;
int exgcd( int a, int b, int &x, int &y )
{
if ( b==0 ) {
x = 1;
y = 0;
return a;
}
int re = exgcd(b,a%b,y,x);
y -= x*(a/b);
return re;
}
signed main()
{
/*
求解 ax + by = c 的通解,和x的最小解
*/
int a,b,c,x,y;
cin>>a>>b;
int gcd = exgcd(a,b,x,y);
if ( c%gcd!=0 ) cout << "无解" << endl;
else {
int bei = c/gcd;
x = x*bei; // 通解需要*倍数
y = y*bei;
int t = b/gcd; // 通解加减的值,不需要*倍数
int ty= a/gcd;
if ( x>=0 ) {
x = x%t; // 得到x的最小正整数解
y = y + (x/t)*ty; // x减 y加
}
else {
x=x%t+t;
y = y - (x/t+1)*ty; // x加 y减
}
cout << x << endl;
}
return 0;
}
4.4 唯一分解定理
因子和 =
int solve( ll x ) // 返回x的因子个数
{
int ans = 1;
for ( int i=0; primes[i]<=sqrt(x)&&i<cnt; i++ ) { // 优化2
int tot = 0; // 这个primes[i]<=sqrt(x)必须优化, 如果是x>0直接T飞
while ( x%primes[i]==0 ) {
x /= primes[i];
tot ++;
}
ans *= (tot+1);
}
if ( x>1 ) ans*=(1+1); // //如果x不能被整分,说明还有一个素数是它的约数,此时tot=1
return ans;
}
4.5 求n以内的组合数
int C( int n, int m )
{
if ( n<m ) return 0;
int ans = ((a[n]*b[m])%mod*b[n-m])%mod;
return ans;
}
a[0]=a[1]=1;
for ( int i=2; i<=1003; i++ ) a[i]=(a[i-1]*i)%mod;
for ( int i=0; i<=1003; i++ ) b[i]=qpow(a[i],mod-2);
5 字符串算法
5.1 KMP
#include <bits/stdc++.h>
#define int long long
using namespace std;
string s,t;
int prefix[200005];
void getpre()
{
int now=-1,i=0;
int len = t.size();
prefix[0] = -1;
while ( i<len-1 ) {
if ( now==-1 || t[i]==t[now] ) {
now ++; i++;
prefix[i] = now;
}
else now=prefix[now];
}
}
void kmp()
{
int i=0,j=0;
int lens = s.size();
int lent = t.size();
while ( i<lens && j<lent ) {
if ( j==-1 || s[i]==t[j] ) {
i++; j++;
}
else j=prefix[j];
}
if ( j>=lent ) cout << i-lent+1 << endl;
else cout << "-1" << endl;
}
signed main()
{
cin>>s>>t; // 在s里找t
getpre();
kmp();
return 0;
}
5.2 字符串hash
#include <bits/stdc++.h>
#define int long long
using namespace std;
typedef unsigned long long ull;
ull base=233317;
ull pwd[2000006];
map<ull,int> mp;
char s[2000005];
signed main()
{
/*
hash[1] = s1;
hash[2] = s1*p + s2;
hash[3] = s1*p^2 + s2*p + s3;
hash[4] = s1*p^3 + s2*p^2 + s3*p + s4;
hash[l,r] = hash[r] - hash[l-1]*p^(r-l+1)
*/
scanf("%s",s+1);s[0]='#';
int len = strlen(s) - 1;
pwd[0] = 0;
for ( int i=1; i<=len; i++ ) pwd[i] = pwd[i-1]*base+s[i];
return 0;
}
5.3 字典树
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 5e5+10;
int trie[maxn][27]; // trie[i][j]=k 编号为j的节点第j个儿子编号时k
int sum[maxn];
string s;
void Insert()
{
int len = s.size();
int root = 0;
for ( int i=0; i<len; i++ ) {
int id = s[i]-'a';
if ( trie[root][id]==0 ) {
trie[root][id] = ++tot;
}
root = trie[root][id];
sum[ root ] ++; // 根据题意灵活变换
}
}
int Search()
{
int len = s.size();
int root = 0;
for ( int i=0; i<len; i++ ) {
int id = s[i]-'a';
if ( trie[root][id]==0 ) return 0;
root = trie[root][id];
}
return sum[root];
}
signed main()
{
int n;cin>>n;
for ( int i=0; i<n; i++ ) {
cin>>s;
Insert();
}
int m;cin>>m;
for ( int i=0; i<m; i++ ) {
cin>>s;
cout << Search() << endl;
}
return 0;
}
6 队列、栈
6.1 单调栈,单调队列
const int maxn = 1e6+10;
struct node {
int date,time;
} v[maxn],t,d; // 模拟单调队列
int n,k;
int a[maxn];
int Max[maxn];
int Min[maxn];
int cnt;
int head=1,tail=0; // 记住这个设定
void getmax()
{
head=1;tail=0;
t.date=a[0]; t.time=k; v[++tail]=t; // 把第一个值放进队列
Max[0] = v[head].date;
for ( int i=1; i<n; i++ ) {
if ( v[head].time<=i ) head++; // 检验队头元素是否过期
t.date = a[i];
t.time = i+k;
while ( t.date>=v[tail].date && head<=tail ) {
tail --; // 当前元素从队尾插入,并保持队列单调
}
v[++tail] = t;
Max[i] = v[head].date; // 更新当前状态的答案
}
for ( int i=k-1; i<n-1; i++ ) {
cout << Max[i] << " ";
}
cout << Max[n-1] << endl;
}
7 线段树/权值线段树/主席树
7.1 线段树
#include<bits/stdc++.h>
#define mid ((left+right)/2)
#define lson node*2,left,mid
#define rson node*2+1,mid+1,right
using namespace std;
const int maxn = 2e5+10;
int a[maxn],tree[maxn*4],lazy[maxn*4];
int n,m;
void up( int node )
{
tree[node] = tree[node*2] + tree[node*2+1];
}
void down( int node, int len )
{
if ( lazy[node] ) {
lazy[node*2] += lazy[node];
lazy[node*2+1] += lazy[node];
tree[node*2] += lazy[node]*(len-len/2);
tree[node*2+1] += lazy[node]*(len/2);
lazy[node] = 0;
}
}
void built_tree( int node, int left, int right )
{
if ( left==right ) {
tree[node] = a[left];
return ;
}
built_tree(lson);
built_tree(rson);
up(node);
}
void update( int node, int left, int right, int L, int R, int x )
{
if ( left>=L && right<=R ) {
tree[node] += x*(right-left+1);
lazy[node] += x;
return ;
}
down(node,right-left+1);
if ( L<=mid ) update(lson,L,R,x);
if ( R>mid ) update(rson,L,R,x);
up(node);
}
int query( int node, int left, int right, int L, int R )
{
if ( left>=L && right<=R ) {
return tree[node];
}
down(node,right-left+1);
int sum = 0;
if ( L<=mid ) sum+=query(lson,L,R);
if ( R>mid ) sum+=query(rson,L,R);
return sum;
}
int main()
{
memset(tree,0,sizeof(tree));
memset(lazy,0,sizeof(lazy));
cin>>n>>m;
for ( int i=1; i<=n; i++ ) scanf("%d",&a[i]);
built_tree(1,1,n);
while ( m-- ) {
char s;cin>>s;
if ( s=='C' ) {
int L,R,x;cin>>L>>R>>x;
update(1,1,n,L,R,x); // [L,R]区间+x
}
else if ( s=='Q' ) {
int L,R;cin>>L>>R;
cout << query(1,1,n,L,R) << endl;
}
}
return 0;
}
/*
10 100
1 1 1 1 1 1 1 1 1 1
*/
7.2 权值线段树
#include <bits/stdc++.h> // 权值线段树
#define mid ((left+right)/2)
#define lson node*2,left,mid
#define rson node*2+1,mid+1,right
using namespace std;
const int maxn = 1e5+10;
int tree[maxn*4];
int a[maxn];
void built_tree()
{
memset(tree,0,sizeof(tree));
}
void update( int node, int left, int right, int x ) // 树中x出现的次数+1
{
if ( left==right ) {
tree[node] ++;
return ;
}
if ( x<=mid ) update(lson,x);
if ( x>mid ) update(rson,x);
tree[node] = tree[node*2]+tree[node*2+1];
}
int query( int node, int left, int right, int L, int R ) // 在区间[L,R]有几个数出现
{
if ( left>=L&&right<=R ) {
return tree[node];
}
int sum = 0;
if ( L<=mid ) sum+=query(lson,L,R);
if ( R>mid ) sum+=query(rson,L,R);
return sum;
}
int kth( int node, int left, int right, int k ) // 返回整个区间第k大的值
{
if ( left==right ) return left;
int rsum = tree[node*2+1];
if ( k<=rsum ) return kth(rson,k);
if ( k>rsum ) return kth(lson,k-rsum);
}
int main()
{
int n;
while ( cin >>n ) {
built_tree();
int now=0,x;
for ( int i=0; i<n; i++ ) {
scanf("%d",&x);
a[i] = x;
now+=query(1,1,n,x+1,n); // 当前逆序对的个数
update(1,1,n,x);
}
//cout << kth(1,1,n,3) << endl;
int ans = now;
for ( int i=n-1; i>=0; i-- ) { // 逆序对的性质
now = now - (n-1-a[i]) + a[i];
ans = min(ans,now);
}
cout << ans << endl;
}
return 0;
}
7.3 主席树
9 动态规划
9.1 背包
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+10;
int dp[maxn];
int weight[maxn],price[maxn];
int n,sum;
signed main()
{
cin>>n; // n个货物
cin>>sum; // 背包容量为sum
for ( int i=1; i<=n; i++ ) cin>>weight[i]>>price[i];
memset(dp,0,sizeof(dp));
/*
恰好装满,求最大价值,memset(dp,-0x3f3f3f3f), dp[0]=0, for里取max
恰好装满,求最小价值,memset(dp,0x3f3f3f3f), dp[0]=0, for里取min
*/
for ( int i=1; i<=n; i++ ) {
for ( int j=sum; j>=1; j-- ) { // 01背包从后往前,完全背包从前往后便利
if ( j>=weight[i] ) {
dp[j] = max(dp[j],dp[j-weight[i]+price[i]);
}
}
}
cout << dp[sum] << endl;
return 0;
}