题目列表:
题目 | 算法 |
---|---|
AcWing 1137. 选择最佳线路 | 最短路+超级源点 |
AcWing 1131. 拯救大兵瑞恩 | 拆点建图+思维+双端队列BFS+状态压缩 |
AcWing 1134. 最短路计数 | 记录最短路条数 |
AcWing 383. 观光 | 求最短路和次短路的方案数,特解,不可拓展 |
P3953 逛公园 | 求最短路等等到第k次短路方案数,通解,可拓展 |
可以直接建反图从终点出发跑最短路。但是这种方法不可拓展,没有什么意思。
这里使用可拓展的方法,可以拓展为多起点多终点选择一个起点终点最短路最优问题。时间复杂度为Dij的 O ( m l o g n ) O(mlogn) O(mlogn),而不是使用Floyd的 O ( n 3 ) O(n^3) O(n3)
方法如下:
建立一个超级源点,源点与各起点连权值为0的边,本题中只有一个终点。(若为多终点,即为二分图匹配问题,需要再建超级汇点,跑最大流)
代码如下:
我顺便用了spfa的SLF优化
#include
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N = 30001, M = 300010, INF = 0x3f3f3f3f;
int ver[M], edge[M], head[N], nex[M], tot = 1;
int n, m, s;
int ed;
int dis[N];
bool vis[N];
void add(int x,int y,int z){
ver[++tot] = y;
edge[tot] = z;
nex[tot] = head[x];
head[x] = tot;
}
int spfa(){
memset(dis,0x3f,sizeof dis);
memset(vis,0,sizeof vis);
deque<int>q;
dis[s] = 0;
vis[s] = true;
q.push_back(s);
while(q.size()){
int x = q.front();
q.pop_front();
vis[x] = false;
for(int i = head[x];i;i = nex[i]){
int y = ver[i],z = edge[i];
if(dis[y] > dis[x] + z){
dis[y] = dis[x] + z;
if(!vis[y]){
vis[y] = true;
if(q.size() && dis[y] < dis[q.front()])
q.push_front(y);
else q.push_back(y);
}
}
}
}
if(dis[ed] == INF)return -1;
return dis[ed];
}
int main(){
while(~scanf("%d%d%d",&n,&m,&ed)){
memset(head,0,sizeof head);
tot = 1;
s = 0;
while(m--){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
}
int w;
scanf("%d",&w);
while(w--){
int x;
scanf("%d",&x);
add(s,x,0);
}
printf("%d\n",spfa());
}
return 0;
}
输入样例
4 4 9
9
1 2 1 3 2
1 2 2 2 0
2 1 2 2 0
2 1 3 1 0
2 3 3 3 0
2 4 3 4 1
3 2 3 3 0
3 3 4 3 0
4 3 4 4 0
2
2 1 2
4 2 1
输出样例
14
从集合角度来分析最优化问题。dp和最短路都可用看成在集合上的最优化问题。
双端队列BFS实际上就是Dijkstra的简化版,既是是最坏的时间复杂度也为线性 O ( n ) O(n) O(n),用于解决权值只有0和1的最短路问题,优于Dijkstra和spfa。
这里的权值就是0(没墙没门),1(有门)的问题,所以使用双端队列BFS解决更优。
#include
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N = 1000, M = 10000, P = 1 << 10, INF = 0x3f3f3f3f;
int ver[M], edge[M], head[N], nex[M], tot = 1;
int n, m, k, p;
int g[N][N];
int ed;
int key[N * N];//当前钥匙的状态
int dis[N][N];//编号+状态
bool vis[N][N];
set<PII>edges;
void add(int x,int y,int z){
ver[++tot] = y;
edge[tot] = z;//这里的edge实际上存的是钥匙的种类,或者0是空地
nex[tot] = head[x];
head[x] = tot;
}
void build(){
//构建所有的可以直接走的空边(空地)(权值为0)
int dx[4] = {
1,-1,0,0}, dy[4] = {
0,0,1,-1};
for(int i = 1;i <= n;++i)
for(int j = 1;j <= m;++j)
for(int u = 0; u < 4;++u){
//四个方向
int nx = i + dx[u],ny = j + dy[u];
if(!nx || nx > n || !ny || ny > m)
continue;
int a = g[i][j], b = g[nx][ny];//取出左边的编号
if(!edges.count({
a,b}))//如果不是墙或者门就添边,0代表是空地
add(a,b,0);
}
}
/*0入队头1入队尾*/
int bfs(){
memset(dis,0x3f,sizeof dis);
memset(vis,0,sizeof vis);//spfa的vis是不用初始化的
dis[1][0] = 0;//起点是1当前状态是0(没有钥匙)
deque<PII>q;
q.push_back({
1,0});
while(q.size()){
PII x = q.front();
q.pop_front();
//点的编号和状态
int id = x.first, now = x.second;
if(vis[id][now])continue;
vis[id][now] = true;
if(id == n * m)//到达终点
return dis[id][now];
/*0有钥匙有门入队头*/
if(key[id]){
//如果这个地方有钥匙就全部拿起来最优
int state = now | key[id];//更新状态,一直累加
if(dis[id][state] > dis[id][now]){
//更新
dis[id][state] = dis[id][now];
q.push_front({
id,state});
}
}
/*1没门没墙可以直接走,入队尾*/
for(int i = head[id];i;i = nex[i]){
int y = ver[i],z = edge[i];
if(z && !(now >> z & 1))//如果有门并且没有这种钥匙
continue;
if(dis[y][now] > dis[id][now] + 1){
dis[y][now] = dis[id][now] + 1;
q.push_back({
y,now});
}
}
}
return -1;
}
int main(){
cin>>n>>m>>p>>k;
for(int i = 1,t = 1;i <= n;++i)
for(int j = 1;j <= m;++j)
g[i][j] = t++;
while(k--){
int x1, y1, x2, y2, z;
scanf("%d %d %d %d %d",&x1, &y1, &x2, &y2, &z);
int a = g[x1][y1],b = g[x2][y2];
edges.insert({
a, b}),edges.insert({
b, a});
if(z)//如果是门就连通路
add(a, b, z),add(b, a, z);
}
build();
int s;
cin>>s;
while(s--){
int x, y, z;
cin >> x >> y >> z;//z 这里我习惯从第0位开始
key[g[x][y]] |= 1 << z;
/*
当前点是否有钥匙,有什么钥匙,
因为一个点可能有多个,所以用|=
*/
}
cout<<bfs()<<endl;
return 0;
}
记录最短路条数
要求最短路计数首先满足条件是不能存在值为0的环,因为存在的话那么被更新的点的条数就为INF了。
要把图抽象成一种最短路树(拓扑图)。
求最短的算法有以下几种
#include
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N = 300000, M = 3000005, INF = 0x3f3f3f3f,mod = 100003;
int n,m;
int ver[M],nex[M],head[N],tot = 1;
int dist[N],cnt[N];//距离和方案数
bool vis[N];
void add(int x,int y){
ver[++tot] = y;
nex[tot] = head[x];
head[x] = tot;
}
queue<int>q;
void bfs(){
memset(dist,0x3f,sizeof dist);
memset(vis,0,sizeof vis);
dist[1] = 0;
cnt[1] = 1;
q.push(1);
while(q.size()){
int x = q.front();
q.pop();
if(vis[x])continue;
vis[x] = true;
for(int i = head[x];~i;i = nex[i]){
int y = ver[i];
if(dist[y] > dist[x] + 1){
dist[y] = dist[x] + 1;
cnt[y] = cnt[x];
q.push(y);
}
else if(dist[y] == dist[x] + 1){
cnt[y] = (cnt[y] + cnt[x]) % mod;
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
memset(head,-1,sizeof head);
while(m--){
int x,y;
scanf("%d%d",&x,&y);
add(x,y),add(y,x);
}
bfs();
for(int i = 1;i <= n;++i){
printf("%d\n",cnt[i]);
}
return 0;
}
注意求次短路的时候,每次更新最短路时顺带更新次短路并讲次短路的相关信息入队。
判断完不能更新最短路的话就判断一下能否更新次短路。
方案的求法:
如果子节点直接被更新,子结点的方案数就直接等于父节点的方案数。
如果子结点长度等于父节点长度 + 两者的距离,那么方案数累加。
当然图里没有子结点父节点这一说,大家自行体会
#include
#include
#include
#include
#include
using namespace std;
const int N = 30000, M = 300005, INF = 0x3f3f3f3f,mod = 100003;
int n,m;
int ver[M],nex[M],edge[M],head[N],tot = 1;
int dist[N][2],cnt[N][2];/*0是最短路,1是次短路*/
bool vis[N][2];
int S,T,t;
struct node{
int id,type,dis;
bool operator<(const node &t)const{
return dis > t.dis;
}
};
void add(int x,int y,int z){
ver[++tot] = y;
edge[tot] = z;
nex[tot] = head[x];
head[x] = tot;
}
int Dijkstra(){
memset(dist,0x3f,sizeof dist);
memset(vis,0,sizeof vis);
memset(cnt,0,sizeof cnt);
dist[S][0] = 0;
cnt[S][0] = 1;//注意这里应该是初始化为 1
priority_queue<node>heap;
heap.push({
S,0,0});
while(heap.size()){
node x = heap.top();
heap.pop();
int id = x.id,type = x. type,distance = x.dis,counts = cnt[id][type];
if(vis[id][type])continue;
vis[id][type] = true;
for(int i = head[id];~i;i = nex[i]){
int y = ver[i],z = edge[i];
/*先更新最短路*/
if(dist[y][0] > distance + z){
dist[y][1] = dist[y][0],cnt[y][1] = cnt[y][0];//次短路继承了最短路更新前的状态
heap.push({
y,1,dist[y][1]});
dist[y][0] = distance + z;
cnt[y][0] = counts;
heap.push({
y,0,dist[y][0]});
}
else if(dist[y][0] == distance + z)cnt[y][0] += counts;
/*再更新次短路*/
else if(dist[y][1] > distance + z){
//如果不能更新最短路,那就看看能否更新次短路
dist[y][1] = distance +z;
cnt[y][1] = counts;
heap.push({
y,1,dist[y][1]});
}
else if(dist[y][1] == distance + z)cnt[y][1] += counts;
}
}
int res = cnt[T][0];
if(dist[T][0] + 1 == dist[T][1])
res += cnt[T][1];
return res;
}
int main(){
cin>>t;
while(t--){
memset(head,-1,sizeof head);
tot = 1;
scanf("%d%d",&n,&m);
for(int i = 1;i <= m;++i){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
}
scanf("%d%d",&S,&T);
printf("%d\n",Dijkstra());
}
return 0;
}