单源最短路的综合应用
(1) 新年好
第一眼,时间复杂度用朴素dijkstra肯定爆炸,只能堆优化版了
这题不是吃甜草的牛那道,每个亲戚可能都不在一条路径上,要找到分支的和,想到找到最短路中最远的一个亲戚,然后不断pre到他的前一个亲戚知道1,如果没有pre到说明不在一条路径上,需要找到没有pre到的点
[!WARNING]
这样想就偏离题意了
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
int n,m;
const int N=1e5+10;
int h[N],e[N],ne[N],w[N],idx;
int st[N],d[N],pre[N];
int vis[5];
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void dijkstra(){
memset(d,0x3f,sizeof d);
priority_queue<PII,vector<PII>,greater<PII> > q;
q.push({0,1});
d[1]=0;
while(q.size()){
auto t=q.top();
q.pop();
int ver=t.second,dist=t.first;
if(st[ver]) continue;
st[ver]=1;
for(int i=h[ver];i!=-1;i=ne[i]){
int j=e[i];
if(d[j]>dist+w[i]){
d[j]=dist+w[i];
pre[j]=ver;
q.push({d[j],j});
cout<<ver<<' '<<j<<' '<<d[j]<<endl;
}
}
}
}
signed main(){
cin>>n>>m;
int a[5];
memset(h,-1,sizeof h);
for(int i=0;i<5;i++) cin>>a[i];
for(int i=0;i<m;i++){
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
add(y,x,z);
}
dijkstra();
int ans=0;
int end=1;
//vis[end]=1;
for(int i=0;i<5;i++){
int maxn=a[i];
for(int j=0;j<5;j++){
if(!vis[a[j]]&&d[a[j]]>d[maxn]){
maxn=a[j];
}
}
cout<<maxn<<' '<<d[maxn]<<endl;
if(!vis[maxn]){
end=maxn;
ans+=d[end];
//vis[maxn]=1;
}
while(end!=1&&!vis[end]){
//cout<<end<<' ';
vis[end]=1;
end=pre[end];
}
//cout<<endl;
}
cout<<ans<<endl;
return 0;
}
仔细分析代码和样例发现,这题和信使(最远最短路)以及牛吃甜草(选一个点到各个点最短距离只和最小)不一样
重新捋一遍思路,发现需要找到一个最优访问亲戚的顺序,那就需要对亲戚的访问顺序做一遍排列
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
int n,m;
//是双向的,M别开小了
const int M=2e5+10,N=5e4+10,INF=0x3f3f3f3f;
int h[M],e[M],ne[M],w[M],idx;
int d[6][N],st[N],vis[N];
int a[6];
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int dfs(int u,int start,int distance){
if(u==6){
return distance;
}
int res=INF;
for(int i=1;i<=5;i++){
if(!vis[i]){
int next=a[i];
vis[i]=1;
res=min(res,dfs(u+1,i,distance+d[start][next]));
vis[i]=0;
}
}
return res;
}
//这里d[]是局部一位数组
void dijkstra(int start,int d[]){
memset(st,0,sizeof st);
memset(d,0x3f,N*4);
priority_queue<PII,vector<PII>,greater<PII> > q;
q.push({0,start});
d[start]=0;
while(q.size()){
auto t=q.top();
q.pop();
int ver = t.second,dist=t.first;
if(st[ver]) continue;
st[ver]=1;
for(int i=h[ver];i!=-1;i=ne[i]){
int j=e[i];
if(d[j]>dist+w[i]){
d[j]=dist+w[i];
q.push({d[j],j});
}
}
}
}
signed main(){
cin>>n>>m;
memset(h,-1,sizeof h);
a[0]=1;
for(int i=1;i<=5;i++) cin>>a[i];
for(int i=0;i<m;i++){
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
add(y,x,z);
}
for(int i=0;i<6;i++) dijkstra(a[i],d[i]);
cout<<dfs(1,0,0)<<endl;
return 0;
}
(2) 通信线路
“指定路径上(1—>N)不超过 𝐾 条电缆”有点bellman_ford的味道 (✅)
“最贵” 先把最难走的路径(1—>N)给公司,然后从剩下的路径中选(❌)
但其实不是bellman_ford算法,因为不是要求在k内抵达N
#include<bits/stdc++.h>
using namespace std;
int n,p,k;
const int N=1e3+10,M=2e4+10;
int h[M],e[M],ne[M],w[M],idx,st[N],d[N];
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int check(int mid){
memset(d,0x3f,sizeof d);
memset(st,0,sizeof st);
deque<int> q;
q.push_back(1);
d[1]=0;
while(q.size()){
int t=q.front();
q.pop_front();
if(st[t]) continue;
st[t]=1;
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i],v=w[i]>mid;
if(d[j]>d[t]+v){
d[j]=d[t]+v;
if(!v) q.push_front(j);
else {
q.push_back(j);
}
}
}
}
return d[n]<=k;
}
signed main(){
cin>>n>>p>>k;
memset(h,-1,sizeof h);
for(int i=0;i<p;i++){
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
add(y,x,z);
}
int l=0,r=1e6+1;
while(l<r){
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
if(r==1e6+1) r=-1;
cout<<r<<endl;
return 0;
}
(3)道路与航线
初看题目,有负边权,那么先不考虑dijkstra。
有两条道路,一种是是可以为负数的航线,一种是只有正数的道路。
“保证如果有一条航线可以从 Ai𝐴𝑖 到 Bi𝐵𝑖,那么保证不可能通过一些道路和航线从 𝐵𝑖 回到 𝐴𝑖”这句话应该是保证数据不可能出现负环。
感觉只能spfa
手搓一个spfa,第一次做这个题,没有ac
#include<bits/stdc++.h>
using namespace std;
int t,r,p,s;
const int N=2e5+10,M=3e5+10,INF=0x3f3f3f3f;
int h[M],e[M],ne[M],w[M],idx;
int st[N],d[N];
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void spfa(){
memset(d,0x3f,sizeof d);
queue<int> q;
d[s]=0;
q.push(s);
while(q.size()){
int t=q.front();
q.pop();
st[t]=1;
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
if(d[j]>d[t]+w[i]){
d[j]=d[t]+w[i];
if(!st[j]){
q.push(j);
}
}
}
}
}
signed main(){
cin>>t>>r>>p>>s;
memset(h,-1,sizeof h);
for(int i=0;i<r;i++){
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
add(y,x,z);
}
for(int i=0;i<p;i++){
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
}
spfa();
for(int i=1;i<=t;i++){
if(d[i]>=INF/2){
puts("NO PATH");
}
else cout<<d[i]<<endl;
}
return 0;
}
ac代码
道路连起来的团看成一个拓扑点,在点内做dijkstra,同时完成拓扑。
求连通块这部分不是很熟悉
连通块代码练习
#include<bits/stdc++.h>
using namespace std;
const int N=1e2+10;
int h[N],e[N],w[N],ne[N],idx;
int bcnt,id[N];
vector<int> block[N];
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u){
//把相邻的点加入同一连通块中
block[bcnt].push_back(u);
//记录每个点所在连通块
id[u]=bcnt;
for(int i=h[u];i!=-1;i=ne[i]){
int j=e[i];
if(!id[j]){
dfs(j);
}
}
}
signed main(){
int n,m;
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);
}
for(int i=1;i<=n;i++){
if(!id[i]){
bcnt++;
dfs(i);
}
}
cout<<bcnt<<endl;
for(int i=1;i<=bcnt;i++){
cout<<i<<endl;
for(int j=0;j<block[i].size();j++){
cout<<block[i][j]<<' ';
}
cout<<endl;
}
return 0;
}
测试
输入:
6 4 1 3 2 3 5 2 2 4 1 2 6 1
输出:
2 1 1 3 5 2 2 6 4
#include<bits/stdc++.h>
using namespace std;
int t,r,p,s;
//M别开小了
const int N=1e5+10,M=2e5+10,INF=0x3f3f3f3f;
typedef pair<int,int> PII;
int id[N],bcnt,din[N];
vector<int> block[N];
queue<int> q;
int h[M],e[M],w[M],ne[M],idx;
int st[N],d[N];
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
//用一个全局变量bcnt记录连通块个数,通过在main函数里判断是否已经被记录来自增
void dfs(int u){
block[bcnt].push_back(u);
id[u]=bcnt;
for(int i=h[u];i!=-1;i=ne[i]){
int j=e[i];
if(!id[j]){
dfs(j);
}
}
}
void dijkstra(int bid){
priority_queue<PII,vector<PII>, greater<PII> > heap;
for(int i=0;i<block[bid].size();i++){
int j=block[bid][i];
heap.push({d[j],j});
}
while(heap.size()){
auto t=heap.top();
heap.pop();
int ver=t.second,dist=t.first;
if(st[ver]) continue;
st[ver]=1;
for(int i=h[ver];i!=-1;i=ne[i]){
int j=e[i];
if(id[ver]!=id[j]&&--din[id[j]]==0) q.push(id[j]);
if(d[j]>dist+w[i]){
d[j]=dist+w[i];
if(id[ver]==id[j])
heap.push({d[j],j});
}
}
}
}
void topesort(){
memset(d,0x3f,sizeof d);
d[s]=0;
//把入度为零的点加入队列中
for(int i=1;i<=bcnt;i++){
if(din[i]==0){
q.push(i);
}
while(q.size()){
int t = q.front();
q.pop();
dijkstra(t);
}
}
}
signed main(){
cin>>t>>r>>p>>s;
memset(h,-1,sizeof h);
for(int i=0;i<r;i++){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
for(int i=1;i<=t;i++){
if(!id[i]){
bcnt++;
dfs(i);
}
}
for(int i=0;i<p;i++){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
din[id[b]]++;
}
topesort();
for(int i=1;i<=t;i++){
if(d[i]>=INF/2) puts("NO PATH");
else cout<<d[i]<<endl;
}
return 0;
}
(4) 最优贸易
最短路计数了解一下
fhq-treap了解一下
反向建图懂了
动态规划和最短路的交集是拓扑关系
不能用dijkstra是因为最小价格(最大价格)是在一条边的两端取,会出现后出现的点可以更新前面的点的情况,所以不能用基于贪心的Dijkstra算法
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=1e5+10,M=1e6+10;
int hs[N],ht[N],e[M],ne[M],idx;
int st[N],dmax[N],dmin[N],w[N];
void add(int *h,int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void spfa(int *d,int start,int *h,bool flag){
queue<int> q;
memset(st,0,sizeof st);
if(flag){
memset(dmin,0x3f,sizeof dmin); //引用可以直接对原数组操作,不能对应用用sizeof
}
d[start]=w[start];
q.push(start);
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(flag&&d[j]>min(d[t],w[j])||!flag&&d[j]<max(d[t],w[j])){
if(flag){
d[j]=min(d[j],w[j]);
}
else{
d[j]=max(d[t],w[j]);
}
if(!st[j]){
q.push(j);
st[j]=1;
}
}
}
}
}
signed main(){
cin>>n>>m;
memset(hs,-1,sizeof hs);
memset(ht,-1,sizeof ht);
for(int i=1;i<=n;i++) cin>>w[i];
for(int i=0;i<m;i++){
int a,b,c;
cin>>a>>b>>c;
add(hs,a,b);add(ht,b,a);
if(c==2){
add(hs,b,a);
add(ht,a,b);
}
}
spfa(dmin,1,hs,1);
spfa(dmax,n,ht,0);
int res=0;
for(int i=1;i<=n;i++){
res=max(dmax[i]-dmin[i],res);
}
cout<<res<<endl;
return 0;
}