Dijkstra(刷题——最短路)
飞行路线
\(n\) 个点 \(m\) 条边的无向连通图,最多可以将 \(k\) 条边的权值赋为 \(0\).求 \(S\) 到 \(t\) 的最短路径
solution
分层图跑最短路,图与图之间的用权值为 \(0\) 的边连接,每下一层代表免费一次,只需求 \(S\) 到 \(t+k*n\)的最短路即可
#include<cstdio>
#include<cctype>
#include<cstring>
#include<queue>
#include<algorithm>
#include<vector>
#include<utility>
#include<functional>
int Read()
{
int x=0;char c=getchar();
while(!isdigit(c))
{
c=getchar();
}
while(isdigit(c))
{
x=x*10+(c^48);
c=getchar();
}
return x;
}
using std::priority_queue;
using std::pair;
using std::vector;
using std::make_pair;
using std::greater;
struct Edge
{
int to,next,cost;
}edge[2500001];
int cnt,head[110005];
void add_edge(int u,int v,int c=0)
{
edge[++cnt]=(Edge){v,head[u],c};
head[u]=cnt;
}
int dis[110005];
bool vis[110005];
void Dijkstra(int s)
{
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > points;
points.push(make_pair(0,s));
while(!points.empty())
{
int u=points.top().second;
points.pop();
if(!vis[u])
{
vis[u]=1;
for(int i=head[u];i;i=edge[i].next)
{
int to=edge[i].to;
if(dis[to]>dis[u]+edge[i].cost)
{
dis[to]=dis[u]+edge[i].cost;
points.push(make_pair(dis[to],to));
}
}
}
}
}
int main()
{
int n=Read(),m=Read(),k=Read(),s=Read(),t=Read();
int u,v,c;
for(int i=0;i<m;++i)
{
u=Read(),v=Read(),c=Read();
add_edge(u,v,c);
add_edge(v,u,c);
for(int j=1;j<=k;++j)
{
add_edge(u+(j-1)*n,v+j*n);
add_edge(v+(j-1)*n,u+j*n);
add_edge(u+j*n,v+j*n,c);
add_edge(v+j*n,u+j*n,c);
}
}
//坑人的数据===================================
for(int i=1;i<=k;++i){
add_edge(t+(i-1)*n,t+i*n);
}
//===========================================
Dijkstra(s);
printf("%d",dis[t+k*n]);
return 0;
}
Telephone Lines S
在加权无向图上求出一条从 \(1\) 号结点到 \(N\) 号结点的路径,使路径上第 \(K+1\) 大的边权尽量小。
solution
二分求最短路,枚举第 \(K+1\) 大的边的长度,如果比枚举的值大,边权就为 \(1\),否则为 \(0\) ,然后跑最短路,如果最短路径大于 \(k\) ,那就合法,继续缩小范围,否则扩大范围
/*
work by:Ariel_
*/
#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
#include <cstring>
#include <utility>
const int M = 2e3 + 5;
int read(){
int x = 0,f = 1;char c = getchar();
while(c < '0'||c > '9'){if(c == '-')f = -1;c = getchar();}
while(c >= '0'&&c <= '9'){x = x*10 + c - '0';c = getchar();}
return f*x;
}
using std::vector;
using std::pair;
using std::make_pair;
using std::priority_queue;
using std::greater;
int n, p, k;
struct edge{
int v,nxt,w;
}e[M << 1];
int cnt,head[M];
void add_edge(int u,int v,int w){
e[++cnt] = (edge){v, head[u], w};
head[u] = cnt;
}
int dis[M];
bool vis[M];
bool dij(int s,int mid){
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[1] = 0;
priority_queue<pair<int,int> ,vector<pair<int, int> >,greater<pair<int ,int> > >q;
q.push(make_pair(0, s));
while(!q.empty()){
int u = q.top().second; q.pop();
if(!vis[u]){
vis[u] = 1;
for(int i = head[u]; i;i = e[i].nxt){
int v = e[i].v;
if(dis[v] > dis[u] + (e[i].w > mid)){
dis[v] = dis[u] + (e[i].w > mid);
q.push(make_pair(dis[v], v));
}
}
}
}
if(dis[n] <= k) return true;
else return false ;
}
int main(){
n = read(),p = read(),k = read();
for(int i = 1, u, v, w;i <= p; i++){
u = read(),v = read(),w = read();
add_edge(u, v, w),add_edge(v, u, w);
}
int l = 0,r = 1000001,ans = 2100000000;
while(l <= r){
int mid = (l + r) >> 1;
if (dij(1, mid)){
ans = mid;
r = mid - 1;
}
else
l = mid + 1;
}
if(ans == 2100000000)printf("-1");
else printf("%d",ans);
}
Cow Party S
\(n\) 个点 \(m\) 条边的有向图,求\(max(min(i~ -> ~x) + min(x ~->~ i))\);
solution
正反图两边最短路
/*
work by:Ariel_
*/
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <utility>
#include <queue>
const int M = 1e5 + 5;
const int N = 1e3 + 2;
int read(){
int x = 0,f = 1;char c = getchar();
while(c < '0'||c > '9'){if(c == '-')f = -1;c = getchar();}
while(c >= '0'&&c <= '9'){x = x*10 + c - '0';c = getchar();}
return f*x;
}
using std::vector;
using std::priority_queue;
using std::make_pair;
using std::pair;
using std::greater;
using std::max;
struct edge{
int v,nxt,w;
}e[M << 1];
int cnt,head[N];
void add_edge(int u,int v,int w){
e[++cnt] = (edge){v,head[u], w};
head[u] = cnt;
}
int dis[N],n, m, x;
bool vis[N];
void dij(int s){
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
dis[s] = 0;
priority_queue<pair<int ,int>,vector<pair<int , int> >,greater<pair<int ,int > > >q;
q.push(make_pair(0, s));
while(!q.empty()){
int u = q.top().second;
q.pop();
if(!vis[u]){
vis[u] = 1;
for(int i = head[u]; i;i = e[i].nxt){
int v = e[i].v;
if(dis[v] > dis[u] + e[i].w){
dis[v] = dis[u] + e[i].w;
q.push(make_pair(dis[v],v));
}
}
}
}
}
int main(){
n = read(),m = read(),x = read();
for(int i = 1,u, v, w;i <= m; i++){
u = read(),v = read(),w = read();
add_edge(u, v, w);
}
int tot[N];
for(int i = 1;i <= n; i++){
dij(i);
tot[i] = dis[x];
}
dij(x);
int ans = 0;
for(int i = 1;i <= n; i++){
ans = max(ans,tot[i] + dis[i]);
}
printf("%d",ans);
}
[USACO06NOV]Roadblocks G
给一张无向图,求这张图的严格次短路之长。
solution
两个 \(dis\) 数组一个存最小值,一个存次小值,维护次小值比最小值大且比其他的边的值小
/*
work by:Ariel_
*/
#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
#include <utility>
#include <cstring>
const int M = 1e5 + 5;
const int N = 5e3 + 4;
int read(){
int x = 0,f = 1;char c = getchar();
while(c < '0'||c > '9'){if(c == '-')f = -1;c = getchar();}
while(c >= '0'&&c <= '9'){x = x*10 + c - '0';c = getchar();}
return f*x;
}
using std::queue;
int n, m;
struct edge{
int v,nxt,w;
}e[M << 1];
int cnt,head[M];
void add_edge(int u,int v,int w){
e[++cnt] = (edge){v, head[u], w};
head[u] = cnt;
}
int dis1[N],opt[N],dis2[N];
bool vis[N];
void spfa(int s){
memset(dis1,0x3f,sizeof(dis1));
memset(dis2,0x3f,sizeof(dis2));
dis1[s] = 0;
queue <int> q;
q.push(s);
while(!q.empty()){
int u = q.front();
q.pop();vis[u] = 0;
for(int i = head[u]; i;i = e[i].nxt){
int v = e[i].v;
if(dis1[v] > dis1[u] + e[i].w){
dis2[v] = dis1[v];
dis1[v] = dis1[u]+e[i].w;
if(!vis[v]) q.push(v),vis[v] = 1;
}
else if(dis1[u] + e[i].w < dis2[v] && dis1[u] + e[i].w > dis1[v]){
dis2[v] = dis1[u] + e[i].w;
if(!vis[v]) q.push(v),vis[v] = 1;
}
if(dis2[u] + e[i].w < dis2[v])
{
dis2[v] = dis2[u] + e[i].w;
if(!vis[v]) q.push(v),vis[v] = 1;
}
}
}
}
int main(){
n = read(),m = read();
for(int i = 1,u,v,w;i <= m; i++){
u = read(),v = read(),w = read();
add_edge(u, v, w),add_edge(v, u, w);
}
spfa(1);
printf("%d", dis2[n]);
}
最短路计数
给出一个 \(N\) 个顶点 \(M\) 条边的无向无权图,顶点编号为 \(1 - N\) 问从顶点 \(1\) 开始,到其他每个点的最短路有几条
solution
跑最短路的时候顺便计数,如果\(dis[v] > dis[u] + 1\) 就重新更新 \(ans[v]\) 的值用 \(ans[u]\) 覆盖
如果\(dis[v] == dis[u] + 1\) 就直接 \(ans[v] += dis[u]\) 就好了
/*
work by:Ariel_
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
const int mod = 100003;
const int N = 1e6 + 5;
const int M = 2e6 + 5;
int read(){
int x = 0,f = 1;char c = getchar();
while(c < '0'||c > '9'){if(c == '-')f = -1;c = getchar();}
while(c >= '0'&&c <= '9'){x = x*10 + c - '0';c = getchar();}
return f*x;
}
using std::queue;
struct edge{
int v,nxt;
}e[M << 1];
int cnt,head[N];
void add_edge(int u,int v){
e[++cnt] = (edge){v,head[u]};
head[u] = cnt;
}
int dis[N],ans[N];
bool vis[N];
void spfa(int x){
memset(dis, 0x3f,sizeof(dis));
queue <int> q;
q.push(x);
dis[1] = 0;
vis[1] = 1;
ans[1] = 1;
while(!q.empty()){
int u = q.front();
q.pop();vis[u] = 0;
for(int i = head[u]; i;i = e[i].nxt){
int v = e[i].v;
if(dis[v] > dis[u] + 1){
dis[v] = dis[u] + 1;
ans[v] = ans[u];
if(!vis[v]){
vis[v] = 1;q.push(v);
}
}
else if(dis[v] == dis[u] + 1){
ans[v] += ans[u];
ans[v] %= mod;
}
}
}
}
int n,m;
int main(){
n = read(),m = read();
for(int i = 1,u, v;i <= m; i++){
u = read(),v = read();
add_edge(u,v);add_edge(v,u);
}
spfa(1);
for(int i = 1;i <= n; i++){
printf("%d\n",ans[i]);
}
}
新年好
无向图,求 \(1\)号点到其余五个点路径和的最小值
solution
很简单,无向图,分别跑出每个点的最短路,全排列暴力即可
因为 \(0x3f\) 卡了半上午……
/*
work by:Ariel_
*/
#include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#include <vector>
const int N = 5e4 + 5;
const int M = 1e5 + 5;
int read(){
int x = 0,f = 1;char c = getchar();
while(c < '0'||c > '9'){if(c == '-')f = -1;c = getchar();}
while(c >= '0'&&c <= '9'){x = x*10 + c - '0';c = getchar();}
return f*x;
}
using std::vector;
using std::pair;
using std::make_pair;
using std::greater;
using std::priority_queue;
using std::min;
using std::cout;
int a[7], n, m;
struct edge{
int v,nxt,w;
}e[M << 1];
int head[N],cnt;
void add_edge(int u,int v,int w){
e[++cnt] = (edge){v, head[u], w};
head[u] = cnt;
}
int dis[N][6];
bool vis[N];
void dij(int x,int num){
memset(vis , 0, sizeof(vis));
for(int i = 1;i <= n; i++) dis[i][num] = 0x3f3f3f3f;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;
dis[x][num] = 0;
q.push(make_pair(0, x));
while(!q.empty()){
int u = q.top().second;
q.pop();
if(vis[u])continue;
vis[u] = 1;
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].v;
if(dis[v][num] > dis[u][num] + e[i].w){
dis[v][num] = dis[u][num] + e[i].w;
q.push(make_pair(dis[v][num], v));
}
}
}
}
int js,bok[7], ans = 0x3f3f3f3f;
void dfs(int x,int tot,int last){
if(x == 5){
ans = min(ans, tot);
return ;
}
for(int i = 1;i <= 5; i++){
if(bok[i]) continue;
bok[i] = 1;
dfs(x + 1,tot + dis[a[i]][last + 1], i);
bok[i] = 0;
}
}
int main(){
n = read(),m = read();
for(int i = 1;i <= 5; i++) a[i] = read();
for(int i = 1,u, v, w;i <= m; i++){
u = read(),v = read(),w = read();
add_edge(u, v, w),add_edge(v, u, w);
}
dij(1, 1);
for(int i = 1;i <= 5; i++) dij(a[i], i + 1);
dfs(0, 0, 0);
printf("%d\n",ans);
}
最优贸易
求一条点和边都可重复的路径,使得路径上存在两点 \(a, b\) ,使得 \(a\) 先比 \(b\) 访问且 \(Vb-Va\) 尽可能地大
solution
分层图最短路,建三层图,在每层图上跑代表没有交易,如果从一层图上了第二层图代表买入,从第二层上到第三层代表卖出,一二层图之间用两点间权值连接,二三层图用负权值连接,跑出最短路,贸易总额就是最短路的相反数
/*
work by:Ariel_
*/
#include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
const int N = 1e5 + 5;
const int M = 5e5 + 5;
int read(){
int x = 0,f = 1;char c = getchar();
while(c < '0'||c > '9'){if(c == '-')f = -1;c = getchar();}
while(c >= '0'&&c <= '9'){x = x*10 + c - '0';c = getchar();}
return f*x;
}
using std::priority_queue;
using std::vector;
using std::pair;
using std::make_pair;
using std::greater;
using std::max;
struct edge{
int v,nxt,w;
}e[M * 3];
int cnt,head[N * 3];
void add_edge(int u,int v,int w){
e[++cnt] = (edge){v,head[u], w};
head[u] = cnt;
}
bool vis[M * 3];
int dis[M * 3];
void dij(int x){
memset(dis, 0x3f3f3f, sizeof(dis));
priority_queue<pair<int ,int>,vector<pair<int , int> >,greater<pair<int ,int> > > q;
q.push(make_pair(0, x));
dis[x] = 0;
while(!q.empty()){
int u = q.top().second;q.pop();
for(int i = head[u];i;i = e[i].nxt){
int v = e[i].v;
if(dis[v] > dis[u] + e[i].w){
dis[v] = dis[u] + e[i].w;
q.push(make_pair(dis[v],v));
}
}
}
}
int n,m,a[N];
int main(){
n = read(),m = read();
for(int i = 1;i <= n; i++) a[i] = read();
for(int i = 1,u, v,flag;i <= m; i++){
u = read(),v = read(),flag = read();
if(flag == 1){
for(int k = 0;k < 3; k++)
add_edge(u + k * n , v + k * n, 0);
add_edge(u, v + n, a[v]);
add_edge(u + n,v + n + n, -a[v]);//卖出去
}
else{
for(int k = 0;k < 3; k++){
add_edge(u + k * n,v + k * n, 0);
add_edge(v + k * n,u + k * n, 0);
}
add_edge(u, v + n, a[v]);
add_edge(v, u + n, a[u]);
add_edge(u + n,v + n * 2, -a[v]);
add_edge(v + n,u + n * 2, -a[u]);
}
add_edge(n, 3 * n, 0);
}
dij(1);
int ans = 0;
ans = max(ans,-dis[n*3]);
printf("%d",ans);
}
汽车加油行驶问题
给你一个\(N*N\)的矩阵,求汽车从\((1,1)\)到\((N,N)\)的最小花费,汽车行进一格需要消耗一点油,汽车油储量为 \(k\).
1.若汽车到达一个有加油站的点,那么必须将油加满为 \(k\),花费为 \(a\);
2.若汽车往回走,即前往的点的横坐标或者纵坐标在减小,需要花费 \(b\);
3.汽车也可以自己新建一个加油站,花费\(c\) (不包括加油费用 \(a\) )
solution
分层图,最短路;\(k\) 的范围很小,我们可以建 \(k + 1\) 层图跑最短路,具体就是以上三种情况
注意特殊情况:恰好到终点,恰好没有,此时不需要加油,所以需要打个特判
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
const int N = 101;
const int dx[4] = {0, 1, 0, -1};
const int dy[4] = {1, 0, -1, 0};
int read(){
int x = 0,f = 1;char c = getchar();
while(c < '0'||c > '9'){if(c == '-')f = -1;c = getchar();}
while(c >= '0'&&c <= '9'){x = x*10 + c - '0';c = getchar();}
return f*x;
}
int n,k,a,b,c;
int mp[N][N],dis[N][N][11];
bool vis[N][N][11];
priority_queue<pair<int,pair<int,pair<int ,int> > > >q;
void dij(){
memset(dis, 0x3f3f3f3f, sizeof(dis));
dis[1][1][k] = 0;
q.push(make_pair(0,make_pair(k,make_pair(1, 1))));
while(!q.empty()){
int z = q.top().second.first,x = q.top().second.second.first,y = q.top().second.second.second;q.pop();//x,y为坐标,z为油量
if(!vis[x][y][z]){
vis[x][y][z] = 1;
for(int i = 0;i < 4; i++){
int nx =x + dx[i],ny = y + dy[i],nz = z - 1,cost = dis[x][y][z];
if(nx > 0&&nx <= n&&ny > 0&&ny <= n){
if(mp[nx][ny] == 1){//强制消费
cost += a;nz = k;
}
if(!nz)//没有油了也没遇见加油站就建一个
cost += c + a,nz = k;
if(i >= 2)//向后走
cost += b;
if(cost < dis[nx][ny][nz]){
dis[nx][ny][nz] = cost;
q.push(make_pair(-cost,make_pair(nz,make_pair(nx, ny))));
}
}
}
}
}
int ans = 0x3f3f3f3f;
if(dis[n][n][k] != 0x3f3f3f3f) dis[n][n][0] = dis[n][n][k] - a - c;//走到终点恰好没有,特判
for(int i = 0;i < k;i++){
ans = min(ans,dis[n][n][i]);
}
printf("%d",ans);
}
int main(){
n = read(),k = read(),a = read(),b = read(),c = read();
for(int i = 1;i <= n; i++){
for(int j = 1;j <= n; j++){
mp[i][j] = read();
}
}
dij();
}