搜索与图论
搜索与图论
DFS
两个经典的暴搜
Q1:数字排序
题目描述
给定一个整数 n,将数字 1∼n排成一排,将会有很多种排列方法。现在,请你按照字典序将所有的排列方法输出。
#include <bits/stdc++.h>
using namespace std;
const int maxn=10;
int q[maxn],vis[maxn],n;
void dfs(int u)
{
if(u==n)
{
for(int i=0;i<n;i++)cout<<q[i]<<" ";
puts("");
}
for(int i=1;i<=n;i++)
{
if(!vis[i])
{
vis[i]=1;
q[u++]=i;
dfs(u);
vis[i]=0;
u--;
}
}
}
int main()
{
cin>>n;
dfs(0);
return 0;
}
Q2:n-皇后问题
题目描述
n−n−皇后问题是指将 nn 个皇后放在 n×nn×n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。

现在给定整数 nn,请你输出所有的满足条件的棋子摆法。
#include <bits/stdc++.h>
using namespace std;
const int maxn=20;
int n,col[maxn],row[maxn],udg[maxn],dg[maxn];
char mp[maxn][maxn];
void dfs(int x,int y,int s)
{
if(y==n)y=0,x++;
if(x==n)
{
if(s==n)
{
for(int i=0;i<n;i++)
{
puts(mp[i]);
}
puts("");
}
return;
}
//放置皇后
dfs(x,y+1,s);
//不放置皇后
if(!col[x]&&!row[y]&&!dg[x-y+n]&&!udg[x+y])
{
col[x]=row[y]=dg[x-y+n]=udg[x+y]=1;
mp[x][y]='Q';
dfs(x,y+1,s+1);
col[x]=row[y]=dg[x-y+n]=udg[x+y]=0;
mp[x][y]='.';
}
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
mp[i][j]='.';
}
}
dfs(0,0,0);
system("pause");
return 0;
}
BFS
两个经典的广搜
Q1:走迷宫
题目描述
给定一个 n×m的二维整数数组,用来表示一个迷宫,数组中只包含 0或 1,其中 0 表示可以走的路,1 表示不可通过的墙壁。
最初,有一个人位于左上角 (1,1) 处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。
请问,该人从左上角移动至右下角 (n,m) 处,至少需要移动多少次。
数据保证 (1,1) 处和 (n,m) 处的数字为 0,且一定至少存在一条通路。
#include <bits/stdc++.h>
using namespace std;
const int maxn=110;
typedef pair<int,int>PII;
int n,m;
int dx[]={1,0,-1,0};
int dy[]={0,-1,0,1};
int vis[maxn][maxn],g[maxn][maxn],d[maxn][maxn];
bool judge(int x,int y)
{
if(x<=0||x>n||y<=0||y>m)return false;
return true;
}
int bfs()
{
memset(vis,0,sizeof(vis));
memset(d,0,sizeof(d));
queue<PII>q;
q.push({1,1});
vis[1][1]=1;
while(!q.empty())
{
auto tmp=q.front();
q.pop();
for(int i=0;i<4;i++)
{
int x=tmp.first+dx[i];
int y=tmp.second+dy[i];
if(!vis[x][y]&&!g[x][y]&&judge(x,y))
{
d[x][y]=d[tmp.first][tmp.second]+1;
vis[x][y]=1;
q.push({x,y});
}
}
}
return d[n][m];
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>g[i][j];
}
}
cout<<bfs()<<endl;
system("pause");
return 0;
}
Q2:八数码
题目描述
在一个 3×3 的网格中,1∼8 这 8 个数字和一个 x 恰好不重不漏地分布在这 3×3 的网格中。
例如:
1 2 3
x 4 6
7 5 8
在游戏过程中,可以把 x 与其上、下、左、右四个方向之一的数字交换(如果存在)。
我们的目的是通过交换,使得网格变为如下排列(称为正确排列):
1 2 3
4 5 6
7 8 x
#include <bits/stdc++.h>
using namespace std;
unordered_map<string,int>d;
int dx[]={1,0,-1,0};
int dy[]={0,1,0,-1};
int bfs(string start)
{
string end="12345678x";
queue<string>q;
q.push(start);
d[start]=0;
while(!q.empty())
{
auto t=q.front();
q.pop();
int distance=d[t];
if(t==end)return distance;
int k=t.find('x');
int x=k/3,y=k%3;
for(int i=0;i<4;i++)
{
int xx=x+dx[i],yy=y+dy[i];
if(xx>=0&&xx<3&&yy>=0&&yy<3)
{
swap(t[k],t[xx*3+yy]);
if(!d.count(t))
{
d[t]=distance+1;
q.push(t);
}
swap(t[k],t[xx*3+yy]);
}
}
}
return -1;
}
int main()
{
string start;
for(int i=0;i<9;i++)
{
char ch;
cin>>ch;
start+=ch;
}
cout<<bfs(start)<<endl;
return 0;
}
树与图的搜索
深搜框架
#include <bits/stdc++.h>
using namespace std;
const int N=100010,M=N*2;
int n,m;
int h[N],e[M],ne[M],idx;
bool st[N];
void add(int a,int b)//连接a和b
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u)
{
st[u]=true;
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(!st[j])dfs(j);
}
}
int main()
{
memset(h,-1,sizeof h);
dfs(1);
}
宽搜框架
#include <bits/stdc++.h>
using namespace std;
const int maxn=100010;
int n,m;
int h[maxn],e[maxn],ne[maxn],idx;
int d[maxn],q[maxn];
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int bfs()
{
memset(d,-1,sizeof d);
queue<int>q;
q.push(1);
d[1]=0;
while(!q.empty())
{
int t=q.front();
q.pop();
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(d[j]==-1)
{
d[j]=d[t]+1;
q.push(j);
}
}
}
return d[n];
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
for(int i=0;i<m;i++)
{
int a,b;
cin>>a>>b;
add(a,b);
}
cout<<bfs()<<endl;
return 0;
}
根据后序与中序遍历构造树
#include <bits/stdc++.h>
using namespace std;
const int MAX_size = 10010;
int post_order[MAX_size], in_order[MAX_size];
int n, minsum = 1000000000, ans = 1;
typedef struct node
{
int data;
struct node* left;
struct node* right;
}node, * pNode;
//通过后序遍历和中序遍历重构二叉树(递归实现)
//读取函数 sstream
bool read_list(int* a)
{
string line;
if (!getline(cin, line))return false;
stringstream ss(line);
n = 0;
int x;
while (ss >> x)
{
a[n++] = x;
}
return n > 0;
}
//构造结点
node* creat_node(int key)
{
node* newnode = (node*)malloc(sizeof(node));
newnode->data = key;
newnode->left = NULL;
newnode->right = NULL;
return newnode;
}
//构造二叉树
pNode build_tree(int* post_order, int* in_order, int N)
{
if (N == 1)
{
return creat_node(*post_order);
}
else if (N > 1)
{
//找到后序的最后一个建立根节点
node* root = creat_node(*(post_order + N - 1));
//找到根节点在中序中的位置
int index = 0;
for (int i = 0; i < N; i++)
{
if (*(in_order + i) == *(post_order + N - 1))
{
index = i;
break;
}
}
//递归求左右子树
root->left = build_tree(post_order, in_order, index);
root->right = build_tree(post_order + index, in_order + index + 1, N - index - 1);
return root;
}
else
{
return NULL;
}
}
//按深度搜索
void dfs(node* root, int sum)
{
if (root->left == NULL && root->right == NULL)
{
sum += root->data;
if (sum < minsum || (sum == minsum && ans > root->data))
{
minsum = sum;
ans = root->data;
}
return;
}
if (root->left != NULL)dfs(root->left, root->data + sum);
if (root->right != NULL)dfs(root->right, root->data + sum);
}
int main()
{
while (read_list(in_order))
{
read_list(post_order);
node* root = build_tree(post_order, in_order, n);//构造二叉树
minsum = 1000000000;
dfs(root, 0);
cout << ans << endl;
}
return 0;
}
拓扑排序
算法思想
- 在有向图中选一个没有前驱的顶点并输出
- 从图中删除该顶点和所有以它为尾的弧
#include <bits/stdc++.h>
using namespace std;
const int N=100010;
int n,m;
int h[N],e[N],ne[N],idx;
int d[N];
int topo[N];
queue<int>q;
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int k=0;
bool toposort()
{
for(int i=1;i<=n;i++)
{
if(!d[i])q.push(i);
}
while(!q.empty())
{
int t=q.front();
q.pop();topo[k++]=t;
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
d[j]--;
if(!d[j])q.push(j);
}
}
return k==n;
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
for (int i = 0; i < m; i++)
{
int a, b;
cin >> a >> b;
add(a, b);
d[b]++;
}
if (toposort())
{
for (int i = 0; i < k; i++) { cout << topo[i] << " "; }
puts("");
}
else puts("-1");
return 0;
}
最短路问题
n是顶点数,m是边数
单源最短路
所有边权都是正数
朴素Dijkstra算法(稠密图 O(n^2))
#include <bits/stdc++.h>
using namespace std;
const int N=510;
int g[N][N];//稠密图用邻接矩阵
int dist[N];//记录每个点和起点的距离
bool st[N];//标记最短距离是否确定
int n,m;
int Dijkstra()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=0;i<n;i++)
{
int t=-1;
//找到距离未确定的点更新现在最小的距离点
for(int j=1;j<=n;j++)
if(!st[j]&&(t==-1||dist[t]>dist[j]))
t=j;
st[t]=true;
//更新每个点到当前最小点的距离
for(int j=1;j<=n;j++)
dist[j]=min(dist[j],dist[t]+g[t][j]);
}
if(dist[n]==0x3f3f3f3f)return -1;
else return dist[n];
}
int main()
{
cin>>n>>m;
memset(g,0x3f,sizeof g);
while(m--)
{
int x,y,z;
cin>>x>>y>>z;
g[x][y]=min(g[x][y],z);
}
cout<<Dijkstra()<<endl;
}
堆优化的Dijkstra算法(稀疏图 O(mlogn))
#include <bits/stdc++.h>
using namespace std;
const int N=150010;
typedef pair<int,int>PII;
int h[N],ne[N],e[N],idx;
int w[N];
int dist[N];
bool st[N];
int n,m;
void add(int a,int b,int c)
{
w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int dijkstra()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
priority_queue<PII,vector<PII>,greater<PII>>heap;
heap.push({0,1});//距离,点编号
while(heap.size())
{
PII k=heap.top();
heap.pop();
int ver=k.second,dis=k.first;
if(st[ver])continue;
st[ver]=true;
for(int i=h[ver];i!=-1;i=ne[i])
{
int j=e[i];
if(dist[j]>dist[ver]+w[i])
{
dist[j]=dist[ver]+w[i];
heap.push({dist[j],j});
}
}
}
if(dist[n]==0x3f3f3f3f)return -1;
else return dist[n];
}
int main()
{
memset(h,-1,sizeof h);
scanf("%d%d",&n,&m);
while(m--)
{
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
}
cout<<dijkstra()<<endl;
return 0;
}
存在负权边
Bellman-Ford算法(O(nm))
#include <bits/stdc++.h>
using namespace std;
const int N=510,M=10010;
int n,m,k;
int dist[N],last[N];//last数组在这里是防止连带更新,保证一次只走一步
struct Edge
{
int a,b,w;
}edges[M];
void bellman_ford()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=0;i<k;i++)
{
memcpy(last,dist,sizeof dist);
for(int j=0;j<m;j++)
{
auto e=edges[j];
dist[e.b]=min(dist[e.b],last[e.a]+e.w);
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=0;i<m;i++)
{
int a,b,w;
scanf("%d%d%d",&a,&b,&w);
edges[i]={a,b,w};
}
bellman_ford();
if(dist[n]>0x3f3f3f3f/2)puts("impossiable");
else printf("%d\n",dist[n]);
return 0;
}
SPFA(O(m),最坏O(nm))
#include <bits/stdc++.h>
using namespace std;
const int N=100010;
int h[N],w[N],e[N],ne[N],idx;
int dist[N];bool st[N];
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int spfa()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
queue<int>q;
q.push(1);
st[1]=true;//记录当前点已经在队列中
while(q.size())
{
int t=q.front();
q.pop();
st[t]=false;
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(dist[j]>dist[t]+w[i])
{
dist[j]=dist[t]+w[i];
if(!st[j])
{
q.push(j);
st[j]=true;
}
}
}
}
return dist[n];
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof(h));
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
int t=spfa();
if(t==0x3f3f3f3f)puts("impossible");
else printf("%d\n",t);
return 0;
}
SPFA判断负环
#include <bits/stdc++.h>
using namespace std;
const int N=100010;
int n,m;
int h[N],e[N],ne[N],w[N],idx;
int dist[N];bool st[N];
int cnt[N];
void add(int a,int b,int c)
{
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
bool spfa()
{
queue<int>q;
for(int i=1;i<=n;i++)
{
st[i]=true;q.push(i);
}
while(q.size())
{
int t=q.front();
q.pop();
st[t]=false;
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(dist[j]>dist[t]+w[i])
{
dist[j]=dist[t]+w[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n)return false;
if(!st[j])
{
st[j]=true;
q.push(j);
}
}
}
}
return true;
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof(h));
for(int i=0;i<m;i++)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
if(spfa())puts("No");
else puts("Yes");
return 0;
}
多源汇最短路
Floyd算法(O(n^3))
//基于动态规划
#include <bits/stdc++.h>
using namespace std;
const int N=210,INF=1e9;
int n,m,Q;
int d[N][N];
void floyd()
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
int main()
{
scanf("%d%d%d",&n,&m,&Q);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i==j)d[i][j]=0;
else d[i][j]=INF;
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
d[a][b]=min(d[a][b],c);
}
floyd();
while(Q--)
{
int a,b;
scanf("%d%d",&a,&b);
int t=d[a][b];
if(t>INF/2)puts("impossible");
else printf("%d\n",t);
}
return 0;
}
最小生成树
Prim算法
朴素版Prim(O(n^2))(稠密图)
#include <bits/stdc++.h>
using namespace std;
const int N=510,inf=0x3f3f3f3f;
int n,m;
int g[N][N],dist[n];
bool st[N];
int prim()
{
memset(dist,0x3f,sizeof dist);
int res=0;
for(int i=0;i<n;i++)
{
int t=-1;
//找到距离集合最小的点
for(int j=1;j<=n;j++)
if(!st[j]&&(t==-1||dist[j]<dist[t]))
t=j;
if(i&&dist[t]==inf)return inf;
if(i)res+=dist[t];
st[t]=true;
for(int i=1;i<=n;i++)dist[j]=min(dist[j],g[t][j]);
}
return res;
}
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]=g[b][a]=min(g[a][b],c);
}
int t=prim();
if(t==0x3f3f3f3f)puts("impossible");
else cout<<t<<endl;
return 0;
}
堆优化版Prim(O(mlogn))
乐色别学,去用优雅的克鲁斯卡尔
Kruskal(O(mlogm))(稀疏图)
思路
- 将所有边按权重从小到大排序
- 从小到大枚举每条边的a,b,c,如果ab不连通,那么就将边加入集合中
#include <bits/stdc++.h>
using namespace std;
const int N=100010,M=200010,inf=0x3f3f3f3f;
int n,m;
int p[N];
struct Edge
{
int a,b,w;
bool operator<(const Edge &W)const
{
return w<W.w;
}
}edges[M];
int find(int x)
{
if(p[x]!=x)p[x]=find(p[x]);
return p[x];
}
int kruskal()
{
sort(edges,edges+m);
for(int i=1;i<=n;i++)p[i]=i;
int res=0,cnt=0;
for(int i=0;i<m;i++)
{
int a=edges[i].a,b=edges[i].b,w=edges[i].w;
a=find(a),b=find(b);
if(a!=b)
{
p[a]=b;
res+=w;
cnt++;
}
}
if(cnt<n-1)return inf;
return res;
}
int main()
{
cin>>n>>m;
for(int i=0;i<m;i++)
{
int a,b,w;
cin>>a>>b>>w;
edges[i]={a,b,w};
}
int t=kruskal();
if(t==inf)puts("impossible");
else printf("%d\n",t);
return 0;
}
知识点补充:c++重载运算符
<返回类型说明符>operator<运算符符号>(<参数表>)
{
<函数体>
}
例:
bool operator<(const Edge &W)const
{
return w<W.w;
}
二分图
定义:当且仅当图中不含有奇数环
染色法(O(n+m))
#include <bits/stdc++.h>
using namespace std;
const int N=100010,M=200010;
int n,m;
int h[N],e[M],ne[M],idx;
int color[N];
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
bool dfs(int u,int c)
{
color[u]=c;
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(!color[j])
{
if(!dfs(j,3-c))return false;
}
else if(color[j]==c)return false;
}
return true;
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--)
{
int a,b;
scanf("%d%d",&a,&b);
add(a,b),add(b,a);
}
bool flag=true;
for(int i=1;i<=n;i++)
{
if(!color[i])
{
if(!dfs(i,1))
{
flag=false;
break;
}
}
}
if(flag)puts("Yes");
else puts("No");
return 0;
}
匈牙利算法(O(mn))
#include <bits/stdc++.h>
using namespace std;
const int N=510,M=100010;
int n1,n2,m;
int h[N],e[M],ne[M],idx;
int match[N];
bool st[N];
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
//让x参与配队,能不能成功
bool find(int x)
{
for(int i=h[x];i!=-1;i=ne[i])
{
int j=e[i];
if(!st[j])
{
st[j]=true;
if(!match[j]||find(match[j]))
{
match[j]=x;
return true;
}
}
}
return false;
}
int main()
{
memset(h,-1,sizeof h);
cin>>n1>>n2>>m;
while(m--)
{
int a,b;
cin>>a>>b;
add(a,b);
}
int res=0;
for(int i=1;i<=n1;i++)
{
memset(st,false,sizeof st);
if(find(i))res++;
}
cout<<res<<endl;
return 0;
}

浙公网安备 33010602011771号