开头先发一个暴论其实不是我说的;
其实搜索DP图论题都是一样的,因为都是建立在图的基础上;
DP就是一种特殊的方式解决一类特殊的问题这类问题是有向边构成的,并且是有topo序的,所以可以用DP去推导出来;
比如说f[i,j]中,这个就可以看成是一个节点,并且由f[i-1,j]和f[i-1,j-v[i]]推过来的;所以说DP是是一种思维方式而不是一种算法;
但是这个怎么建模怎么去推就是外话了;
搜索中,最短路就不用说了,最小步数的问题也是可以看成1个状态是一个节点;那么能转移到的状态之间连上一条边;
每个搜索的题目都会产生一棵搜索树,为了让我们搜索的效率更高,对此我们需要各种各样的搜法;
最短路倞问题的搜索树就是原来的图,而最小步数问题的搜索树会更难想一点,每个节点都是一个状态,比如说填数独那道题,你看着最后一个状态的前一步可以由很多种方式转移过来,数独上的每个点都可以少掉;所以有几个格子他就能由几种状态转移过来;
在第一版的博客里面我是没有讲搜索树的,现在补上去;
当然搜索树搜索图都一样,树就是图;
DFS能解决的不只是最小步数和最短路倞,以上两个是一定有搜索树的,
图论就不用说了;
优先队列BFS和Dijkstra
为什么把这两个放在一起讲,因为这两个特别像;
优先队列每次都把当前已经确定的点中选出一个与原点距离最短的出来然后再把这个玩意用来更新所有没有确定的点;
dijkstra也是这样;
这个搜索图就是普通的正权图,不能有负权的说法其实不完全对,比如下面一种情况DIJKSTRA也能算;
如果你觉得不能算你可以去试一试,反正我是试过了;
为什么说DIJKSTRA不能搜索大部分的负权图,因为他要符合贪心的性质,对吧,都是正权的情况下肯定是行的,如果有负权就不行了,比如下面这种
如果你觉得行的话你可以去试一试,这就不满足贪心选择性质了;
DIJKSTRA也不能求负环,因为dist是会定下来的,定下来就不去变了
它是看vis数组来进行入队出对操作而不是像SPFA一样通过每次松一下来执行的;当然如果你把堆优化DIJKSTRA改成了优先队列SPFA那么也是可以求的,没改几行;
代码:
朴素算法dijkstra:
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510;
int n, m, g[N][N], dist[N];
bool st[N];
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < n - 1; i ++ )
{
int t = -1;
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
for (int j = 1; j <= n; j ++ )
dist[j] = min(dist[j], dist[t] + g[t][j]);
st[t] = true;
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
scanf("%d%d", &n, &m);
memset(g, 0x3f, sizeof g);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a][b] = min(g[a][b], c);
}
printf("%d\n", dijkstra());
return 0;
}
堆优化dijkstra:(之前代码CE的现在已经修正了)
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const int N=100010,M=1000010;
int head[N],ver[M],edge[M],ne[M],d[N];
bool vis[N];
int n,m,tot;
priority_queue < pair<int,int > >q;
void add(int x,int y,int z){
ver[++tot]=y;edge[tot]=z;ne[tot]=head[x];head[x]=tot;
}
void dijkstra() {
memset(d,0x3f,sizeof d);
memset(vis,0,sizeof vis);
d[1]=0;
q.push({
0,1});
while(q.size()){
int x = q.top().second; q.pop();
if(vis[x])continue;
vis[x]=1 ;
for(int i=head[x];i;i=ne[i]){
int y=ver[i],z=edge[i];
if(d[y] > d[x]+z){
d[y] = d[x] + z;
q.push({
-d[y],y});
}
}
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
}
dijkstra();
for(int i=1;i<=n;i++){
printf("%d\n",d[i]);
}
return 0;
}
优先队列的bfs就和这个相似;
双端队列
就是用deque来实现的bfs,因为在边权为01的情况下queue里面的东西满足单调性和两段性的;所以可以从front插入,也可以从back插入;能省很多时间;
但是呢这个搜法比较鸡肋;因为要两段性,所以只能在边权为01的情况下能用;可以看成是priority_queue的特殊情况;
例题
这题比较操蛋;难以转化题目给的条件;可以将题目中给的链接方式边权为0;如果遇到要转动的时候边权就为1;0入对头,1入队尾;每次从队头取出元素;这样子就能保证在0没有取完的时候不会取到1,1没有取完的时候不会取到2;
听说用 堆优化过的dijkstra 也能做;因为25W个点,所以必须堆优化一下;优化之后应该会过,数量级是1-2千万级别的;
大算法就是搜索,小细节就是题目条件的转化和每个条件的存储和表示;
因为小细节还是比较重要的所以特地copy了一下y总的代码:
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
typedef pair<int,int> PII;
const int N=510 , M= N*N;
int n,m,dist[N][N];
char g[N][N];
bool st[N][N];
int bfs(){
memset(dist,0x3f,sizeof dist);
memset(st,0,sizeof st);
dist[0][0] = 0;
deque<PII> q;
q.push_back({
0,0}) ;
char cs[]="\\/\\/";
int dx[4]={
-1,-1,1,1}, dy[4]={
-1,1,1,-1};
//d数组储存的是从一个点到4个斜向的方向的变化距离;
int ix[4]={
-1,-1,0,0}, iy[4]={
-1,0,0,-1};
//i数组储存的是从一个点到四周的格子的距离;
while(q.size()){
PII t=q.front();
q.pop_front();
if(st[t.first][t.second])continue;
st[t.first][t.second] = true;
for(int i=0;i<4;i++){
int a=t.first+dx[i] , b=t.second+dy[i];
if(a<0||a>n||b<0||b>m)continue;//点越界了
int ca=t.first+ix[i], cb = t.second+iy[i];//枚举格子
int d = dist [t.first][t.second]+ (g[ca][cb]!=cs[i]);
// !=用的很灵性,如果不等于结果为真值为1;
if (d<dist [a][b]){
dist [a][b] = d;
if(g[ca][cb]!=cs[i]) q.push_back({
a,b});
else q