个人博客网站:个人博客网站(actoyouai.com)
前言:帮助大家学习算法,学习C++
1. 单调队列
板子:
for(int i=0;i<n;i++)
{
//dq.push_back(i);不可以放前面,否则a[dq.back()] < a[i]就是执行的a[i]< a[i]
while(!dq.empty() && a[dq.back()] < a[i])
dq.pop_back();
if(!dq.empty() && i-dq.front()>=k)
dq.pop_front();
dq.push_back(i);
if(i>=k-1)
cout<<a[dq.front()]<<" ";
}
滑动窗口模板题:ac.nowcoder.com/acm/problem…
/*
1. 需要维护:尾部插入,删除,头部删除,查找-->所以用deque
2. deque队列维护的是下标值,不是值
*/
#include<bits/stdc++.h>
using namespace std;
int n,k;
int a[1000010];
deque<int> dq;
int main()
{
cin>>n>>k;
for(int i=0;i<n;i++)
{
cin>>a[i];
}
for(int i=0;i<n;i++)
{
while(!dq.empty() && a[dq.back()] > a[i])
dq.pop_back();
if(!dq.empty() && i-dq.front()>=k)
dq.pop_front();
dq.push_back(i);
if(i>=k-1)
cout<<a[dq.front()]<<" ";
}
cout<<endl;
dq.clear();
for(int i=0;i<n;i++)
{
//dq.push_back(i);不可以放前面,否则a[dq.back()] < a[i]就是执行的a[i]< a[i]
while(!dq.empty() && a[dq.back()] < a[i])
dq.pop_back();
if(!dq.empty() && i-dq.front()>=k)
dq.pop_front();
dq.push_back(i);
if(i>=k-1)
cout<<a[dq.front()]<<" ";
}
//错误:不可以存值,存下标不会出错
/*
输入:
8 3
8 7 6 7 7 6 5 4
输出:
6 6 6 6 5 4
8 8 7 7 7 6
*/
// for(int i=0;i<n;i++)
// {
// while(!dq.empty() && dq.back() < a[i])
// dq.pop_back();
// if(!dq.empty() && dq.size()>=k)
// dq.pop_front();
// dq.push_back(a[i]);
// if(i>=k-1)
// cout<<dq.front()<<" ";
// }
return 0;
}
2.BFS
例题:经典八数码问题:ac.nowcoder.com/acm/problem…
#include<bits/stdc++.h>
using namespace std;
const int N=363000;
string st[N];
unordered_map<string,int > mp;
int last[N];//表示上一个当前状态的上一状态,用于输出移动步骤
bool used[N];//表示每一个状态是否使用过
int cnt=1;
string s,t="12345678x";
int dx[5]={0,1,-1,0,0},dy[5]={0,0,0,1,-1};//下上右左
char dic[10]={'0','d','u','r','l'};
char mv[N];
queue<int> q;
bool bfs(string s,string t)
{
//初始s代表状态1
mp[s]=1;
st[1]=s;
used[1]=1;
q.push(1);
last[1]=1;//不能有(如果选择dfs输出)
while(!q.empty())
{
int tmp= q.front();//当前状态字符串对应的数字
q.pop();
string now_st=st[tmp];//当前状态字符串
//cout<<"now_st="<<now_st<<endl;
if(now_st==t)//最终状态
{return true;break;}
int k=now_st.find('x');
int pos_x=k/3,pos_y=k%3;
for(int i=1;i<=4;i++)//4个位置移动
{
int a=pos_x+dx[i],b=pos_y+dy[i];
//移动后的状态字符串
if(a>=0&&b>=0&&a<=2&&b<=2)
{
int move_x_pos=a*3+b;
string nx=now_st;
//cout<<k<<" "<<move_x_pos<<endl;
swap(nx[k],nx[move_x_pos]);
if(mp.find(nx)==mp.end())//表示第一次到达这个状态
{
mp[nx]=++cnt;
st[cnt]=nx;
used[cnt]=1;
last[cnt]=tmp;
mv[cnt]=dic[i];
if(nx==t) {return true;break;}
q.push(cnt);//下一个状态入队
}
//swap(now_st[k],now_st[move_x_pos]);
}
}
}
return false;
}
void dfs(int t)
{
if(last[t]!=0)
{
dfs(last[t]);
cout<<mv[t];
}
}
int main()
{
stack<char> sc;
char c;
for(int i=0;i<9;i++)
{
cin>>c;
s+=c;
}
if(!bfs(s,t))
cout<<"unsolvable";
else
{
//cout<<mp[t];
// dfs(mp[t]);
for(int i=cnt;i!=1;i=last[i])
{
sc.push(mv[i]);
}
while(!sc.empty())
{
cout<<sc.top();
sc.pop();
}
}
return 0;
}
对于本题变形有时候可能只需要输出走的步数而不需要输出如何走的,那么可以简化为:
#include <iostream>
#include <algorithm>
#include <queue>
#include <unordered_map>
#include <map>
using namespace std;
int bfs(string start)
{
//定义目标状态
string end = "12345678x";
//定义队列和dist数组
queue<string> q;
unordered_map<string, int> d;
//初始化队列和dist数组
q.push(start);
d[start] = 0;
//转移方式
int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1};
while(q.size())
{
auto t = q.front();
q.pop();
//记录当前状态的距离,如果是最终状态则返回距离
int distance = d[t];
if(t == end) return distance;
//查询x在字符串中的下标,然后转换为在矩阵中的坐标
int k = t.find('x');
int x = k / 3, y = k % 3;
for(int i = 0; i < 4; i++)
{
//求转移后x的坐标
int a = x + dx[i], b = y + dy[i];
//当前坐标没有越界
if(a >= 0 && a < 3 && b >= 0 && b < 3)
{
//转移x
swap(t[k], t[a * 3 + b]);
//如果当前状态是第一次遍历,记录距离,入队
if(!d.count(t))
{
d[t] = distance + 1;
q.push(t);
}
//还原状态,为下一种转换情况做准备
swap(t[k], t[a * 3 + b]);
}
}
}
//无法转换到目标状态,返回-1
return -1;
}
int main()
{
string c, start;
//输入起始状态
for(int i = 0; i < 9; i++)
{
cin >> c;
start += c;
}
cout << bfs(start) << endl;
return 0;
}
3.DFS
例题:高手去散步 - 洛谷
#include<bits/stdc++.h>
using namespace std;
int n,m,d;
bool used[110];
int ans;
vector<pair<int,int> > v[50];//用于存储某一个点能到达的下一个点,以及他们之间的距离
bool check(int now)
{
if(v[now].size()==0) return false;
for(auto u:v[now])
{
if(used[u.first]==0) return false;//表示有后续点没有被访问过
}
return true;
}
void dfs(int s,int t,int now)
{
if(now==t || check(now))//递归结束条件 :1. 到达终点 或者 2. 当前点的后续能到达的点都已经被访问过
{
ans=max(ans,d);
return ;
}
for(auto u: v[now])//遍历当前点能到达的下一个点
{
int ne=u.first,dist=u.second;
if(used[ne]==0 )
{
d+=dist;
used[ne]=1;
dfs(s,t,ne);
d-=dist;
used[ne]=0;
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int x, y,d;
cin>>x>>y>>d;
v[x].push_back({y,d}); //x可以到达y,距离为d
v[y].push_back({x,d});
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(i==j) continue;
d=0;
if(v[i].size()!=0)
{
used[i]=1;//标记起点已经访问
dfs(i,j,i);//起点,终点,当前点
used[i]=0;//一定记得回溯起点
}
}
cout<<ans;
return 0;
}
例题:海战 - 洛谷
#include<bits/stdc++.h>
using namespace std;
int n,m;
bool used[1010][1010];
char ch[1010][1010];
int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
int ans;
/*关键在于判断什么是合法的
如果图是不和法的,一定存在如下结构:
# #
. #
或
# #
# .
或
# .
# #
或
. #
# #
即在一个2*2的方格中有三个#。所以就能得出代码
*/
bool d(int i,int j){
int c=0;
if(ch[i][j]=='#')c++;
if(ch[i+1][j]=='#')c++;
if(ch[i][j+1]=='#')c++;
if(ch[i+1][j+1]=='#')c++;
if(c==3)return 0;
return 1;
}//判断是否合法
void dfs(int x,int y)
{
used[x][y]=1;
for(int i=0;i<4;i++)
{
int mx=x+dx[i],my=y+dy[i];
if(mx>=1 && mx<=n && my>=1 && my<=m && ch[mx][my] != '.' && used[mx][my]==0)
{
dfs(mx,my);
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>ch[i]+1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(i<n&&j<m&&d(i,j)==0){
printf("Bad placement.");
return 0;//不合法后面就没必要继续了
}
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(ch[i][j]!='.' && used[i][j]==0)
{
ans++;
dfs(i,j);
}
}
cout<<"There are "<<ans<<" ships.";
return 0;
}
4.后缀最大值
例题:栈与排序:ac.nowcoder.com/acm/problem…
#include<bits/stdc++.h>
using namespace std;
int n;
int a[1000010],b[1000010];
stack<int> s;
int cmp(int a,int b)
{
return a>b;
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
cin>>a[i];
//b[i]=a[i];
}
//memcpy(b,a,n);//不行
//错误方法:不能排序后每一次看栈顶是否是当前最大值
// sort(b,b+n,cmp);
// 5
// 10 9 8 10 8
//走到第2个10的时候10出栈,栈顶8,按照错误方法10出栈后,当前最大为9,继续8进栈,输出10 10 8 8 9
//正确的为10 10 8 9 8
for(int i=n-1;i>=0;i--)
b[i]=max(b[i+1],a[i]);//后缀最大值
// for(int i=0;i<n;i++)
// cout<<b[i]<<" ";
// cout<<endl;
for(int i=0;i<n;i++)
{
s.push(a[i]);
while( (!s.empty()) && s.top()>=b[i+1] )
//必须加1 b[i+1],错误示例也见上面的,因为s.top()>=b[i],
//会导致s.top()输出之后,此后栈里面的栈顶元素和当前已经输出的栈顶比较大小,而不是后面的未入栈的元素
{
int x=s.top();
s.pop();
cout<<x<<" ";
}
}
while(!s.empty())
{
int x=s.top();
cout<<x<<" ";
s.pop();
}
return 0;
}
5.搜索剪枝
优化搜索的核心:
- 减小搜索树的大小改变搜索顺序
- 最优化剪枝:每次搜索完成后更新当前得到的最优解,在每次搜索开始前判断当前解是否已经比上次得出的解更差?如果更差是则停止本次搜索,转向其他搜索分支。
- 可行性剪枝
例题:ac.nowcoder.com/acm/problem…
//枚举长木棍的长度
/*
剪枝:
1.将所有题目给的棍子的长度按照从大到小的顺序排列,然后按照此顺序进行深搜,优化搜索顺序
2.如果第i个棍子不能拼成假设的长度,则和第i个棍子相同长度的棍子也是不可能的,所以可以直接跳过去的!
3.替换第i根棍子的第一根木棒是没用的
4.如果某次拼接选择长度为S 的木棒,导致最终失败,则在同一位置尝试下一根木棒时,要要跳过所有长度为S 的木棒。
*/
#include<bits/stdc++.h>
using namespace std;
int N,ans;
int a[66],vis[65];
int min_len;//长棍子最短长度枚举起点
int sum_len;
bool cmp(int a,int b)
{
return a>b;
}
bool dfs(int num, int rest, int len, int now)
{
//num剩余小棍数;rest当前拼接的大棍剩余长度;len需要拼接的大棍长度;now正在拼接的是哪个棍子
if(num==0 && rest==0) return true;//得到一个len值,即可以拼接完成
if(rest==0) //当前拼接的长度达到了我们枚举假设出来的大棍长度 ,进行下一根小棍的拼接
{
rest=len;
now=1;
}
for(int i=now;i<=N;i++)
{
if(vis[i]) continue;
if(a[i]>rest) continue;//剪枝:放不下当前的小棍a[i]
vis[i]=1;
if(dfs(num-1, rest - a[i], len, i))//拼下一个位置
return true;
vis[i]=0;//还原
if(rest == len || rest == a[i])
break;
while(a[i]==a[i+1])//剪枝-2
i++; //此处写continue不行
}
return false;
}
int main()
{
cin>>N;
for(int i=1;i<=N;i++)
{
cin>>a[i];
sum_len+=a[i];
if(a[i]>min_len) min_len=a[i];
}
sort(a+1,a+1+N,cmp);//此处相当于剪枝-1:优化搜索顺序
for(ans = min_len; ans <= sum_len; ans++)
{
if(sum_len % ans !=0) continue;//判断整除也相当于剪枝
if(dfs(N,ans,ans,1)) break;
}
cout<<ans;
return 0;
}
6.二进制枚举(一般都可以用DFS解决)
例题:[USACO23JAN] Air Cownditioning II B
#include<bits/stdc++.h>
using namespace std;
const int N=25,M=20;
int n,m,ans=1e9,money;
int s[N],t[N],c[N],d[115],st[115],d_back[115];
int a[M],b[M],p[M],mi[M];
bool check()
{
for(int i=0;i<=111;i++)
{
if(st[i]>0)
return false;
}
return true;
}
int main()
{
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>s[i]>>t[i]>>c[i];
d[s[i]]+=c[i];
d[t[i]+1]-=c[i];
}
//差分数组测试
/*
for(int i=1;i<=100;i++)
{
cout<<d[i]<<" ";
}
cout<<endl;
for(int i=1;i<=100;i++)
{
st[i]=st[i-1]+d[i];
cout<<st[i]<<" ";
}
*/
//结束差分测试
for(int i=1;i<=m;i++)
cin>>a[i]>>b[i]>>p[i]>>mi[i];
for(int i=0; i < (2 << m-1) ; i++)//m位 全0到全1 二进制枚举0 <= i <= 2^(m-1)-1 数量级<=10^3
{
money=0;
memset(st,0,sizeof (st));
for(int i=0;i<=110;i++) 10^2
d_back[i]=d[i];
//每一次枚举前需要清零还原
for(int k=0;k<m;k++) //m<=10
{
if(( i >> k) &1==1)//第i个空调选择
{
// cout<<"i选择"<<i<<endl;
// cout<<"-------------------------------------\n";
d_back[a[k+1]]-=p[k+1];
d_back[b[k+1]+1]+=p[k+1];//此处应该是二进制枚举的哪一个位置选择既第k+1位(下标从1开始的所以+1)
money+=mi[k+1];
// cout<<a[k+1]<<' '<<b[k+1]+1<<endl;
}
}
for(int i=1;i<=111;i++) 10^2
st[i]=st[i-1]+d_back[i];
if(check()) ans=min(ans,money);
}//时间复杂度10^5
cout<<ans;
return 0;
}
7.优先队列
例题:ac.nowcoder.com/acm/problem…
//换成优先队列,按照离起点距离最近的先取出,
//对于后面再次出现的已经被更新的点直接忽略,先入优先队列的一定和顶点距离更近
//传送过去的点不能标记,也就是更新d数组,卡了我好久这个,也就是第68行
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
PII S,T;
int n,m,q;
int x,x2,y,y2;
int g[321][321],d[321][321];
vector<PII> vp[321][321];
struct Node{
int dist;
int x,y;
bool operator<(const Node &s)const{
return dist>s.dist;
}
};
char ch;
//bool vis[321][321]; //不要,d数组就相当于标记了
int dx[5]={0,0,1,0,-1},dy[5]={0,1,0,-1,0};
priority_queue<Node> pqn;
void bfs()
{
Node start;
start.x=S.first,start.y=S.second;
start.dist=0;
pqn.push(start);
//vis[S.first][S.second]=1;
d[S.first][S.second]=0;
while(!pqn.empty())
{
Node now=pqn.top();
pqn.pop();
int pos_x=now.x,pos_y=now.y,pos_dis=now.dist;
if(pos_x==T.first && pos_y==T.second)
{
cout<<pos_dis<<endl;
return ;
}
for(int i=1;i<=4;i++)
{
int go_x=pos_x+dx[i],go_y=pos_y+dy[i];
if(go_x>=1 && go_x <=n && go_y>=1 && go_y <=m && d[go_x][go_y]==-1 &&g[go_x][go_y]!=0)//代表第一次到达这个点
{
Node tmp;
tmp.x=go_x,tmp.y=go_y,tmp.dist=1+pos_dis;
d[go_x][go_y]=1+pos_dis;//实际就是vis的作用,并没有存储所有的距离;d[go_x][go_y]=1也能ac
pqn.push(tmp);
// cout<<pos_x<<" "<<pos_y<<" NO "<<i<<"up down"<<go_x<<" "<<go_y<<endl;
}
}
for(auto u:vp[pos_x][pos_y])
{
if(! (d[u.first][u.second]==-1 && g[u.first][u.second]!=0) ) continue;
Node tmp;
tmp.x=u.first,tmp.y=u.second,tmp.dist=3+pos_dis;
//d[tmp.x][tmp.y]=3+pos_dis;//不能要
/*
我们传送过去的点不能做标记!!!
因为如果我们靠传送过去,并不是最短的方法
举个栗子:某一点是传送门,传送到他右边的第一个位置。
如果传送,我们要三秒。如果走路,我们只要一秒。标记了我们就走不了了啦。
*/
pqn.push(tmp);
// cout<<"trans"<<tmp.x<<" "<<tmp.y<<endl;
}
}
cout<<-1<<endl;
}
int main()
{
while(cin>>n>>m>>q)
{
while(!pqn.empty()) pqn.pop();
memset(g,0,sizeof g);
//memset(vis,0,sizeof vis);
memset(d,-1,sizeof d);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
cin>>ch;
if(ch!='#') g[i][j]=1;
if(ch=='S') S={i,j};
if(ch=='T') T={i,j};
vp[i][j].clear();
}
// for(int i=1;i<=n;i++)
// {
// for(int j=1;j<=m;j++)
// cout<<g[i][j]<<" ";
// cout<<endl;
// }
for(int i=1;i<=q;i++)
{
cin>>x>>y>>x2>>y2;
x++,y++,x2++,y2++;
vp[x][y].push_back({x2,y2});
}
bfs();
}
return 0;
}