最短路习题题解-三连击p.s
Telephone Lines 二分答案+最短路
Solution:
钱的花费显然具有单调性,即花更多的钱得到的方案中,一定包含花费更少的方案。
所以可以二分答案,转化为判定性问题。
然后我们发现很简单了,只需每次check边权大于当前二分的钱的边的数量是不是小于等于K即可。
Code ↓ :
IL bool spfa() {
RG int i,x,y;
memset(inq,0,sizeof(inq));
memset(dis,0x3f,sizeof(dis));
q.push(1),dis[1]=0,inq[1]=1;
while (!q.empty()) {
x=q.front(),q.pop(),inq[x]=0;
for (i=head[x];i;i=e[i].next)
if (dis[y=e[i].to]>dis[x]+e[i].ver) {
dis[y]=dis[x]+e[i].ver;
if (!inq[y]) q.push(y),inq[y]=1;
}
}
return dis[n]<=k;
}
IL bool check() {
RG int i;
tot=0;
memset(&e,0,sizeof(e));
memset(head,0,sizeof(head));
for (i=1;i<=m;++i) make(fr[i],to[i],(v[i]>mid));
return spfa();
}
Roads and Planes Topo序+dijkstra
Solution:
直接spfa过不了。。。dijkstra又处理不了负边权。。。怎么办⊙_⊙
注意到所有的无向边不为负,那么我们可以先只考虑连上无向边,得到若干连通块。
那么,对于每个连通块,显然是能够用dijkstra得到当前连通块内各点的最短路的。
然后再考虑加上有向边。
如果把每个连通块看成一个点,那么这张图就是一张DAG。
那么就可以做Topo排序,把连通块之间的最短路关系传递一下就可以了。
Code↓:
#include<bits/stdc++.h>
#define RG register
#define IL inline
#define DB double
#define LL long long
#define mp make_pair
using namespace std;
IL int gi() {
RG int x=0,w=0; char ch=0;
while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return w?-x:x;
}
const int N=25001;
const int M=50001;
const int INF=0x3f3f3f3f;
vector<int> p[N];
queue<int> q1;
priority_queue< pair<int,int> > q2;
int T,R,P,S,num,tot,head[N],bel[N],ind[N],vis[N],dis[N];
struct Edge{int next,to,ver;}e[M<<2];
IL void make(int a,int b,int c) {e[++tot]=(Edge){head[a],b,c},head[a]=tot;}
void dfs(int x) {
RG int i,y;
bel[x]=num,p[num].push_back(x);
for (i=head[x];i;i=e[i].next)
if (!bel[y=e[i].to]) dfs(y);
}
int main ()
{
RG int i,hd,x,y,z;
T=gi(),R=gi(),P=gi(),S=gi();
for (i=1;i<=R;++i)
x=gi(),y=gi(),z=gi(),make(x,y,z),make(y,x,z);
for (i=1;i<=T;++i)
if (!bel[i]) ++num,dfs(i);
for (i=1;i<=P;++i) {
x=gi(),y=gi(),z=gi(),make(x,y,z);
if (bel[x]!=bel[y]) ++ind[bel[y]];
}
memset(dis,127,sizeof(dis));
dis[S]=0,q1.push(bel[S]);
for (i=1;i<=num;++i)
if (!ind[i]) q1.push(i);
while (!q1.empty()) {
hd=q1.front(),q1.pop();
for (i=0;i<p[hd].size();++i)
q2.push(mp(-dis[p[hd][i]],p[hd][i]));
while (!q2.empty()) {
x=q2.top().second,q2.pop();
if (vis[x]) continue;
vis[x]=1;
for (i=head[x];i;i=e[i].next) {
if (dis[y=e[i].to]>dis[x]+e[i].ver) {
dis[y]=dis[x]+e[i].ver;
if (bel[x]==bel[y]) q2.push(mp(-dis[y],y));
}
if (bel[x]!=bel[y]&&--ind[bel[y]]==0) q1.push(bel[y]);
}
}
}
// 整体Topo排序 局部dijkstra
for (i=1;i<=T;++i)
if (dis[i]>0x3f3f3f3f) puts("NO PATH");
else printf("%d\n",dis[i]);
return 0;
}
Cow Relays Floyd+矩阵乘法
Solution:
先把边给离散化一下。
不妨设邻接矩阵A[k]代表恰好经过k条边的情况。
具体而言就是:这个矩阵中一对点i,j,A[k][i][j]表示的是恰好经过K条边的i,j间的最短路。
考虑怎么转移过来,由Floyd算法可知:
A[k][i][j]=min{A[x][i][p]+A[y][p][j]};
其中x+y=k。
诶,好像和矩阵乘法的形式很像啊:
O[i][j]=sum{R[i][k]×Z[k][j]};
同时注意到,这个矩阵A也满足结合律。
所以可以相当于只是把矩阵乘法的求和改为取min,乘积改为求和了。
Code↓:
#include <bits/stdc++.h>
#define RG register
#define IL inline
#define LL long long
using namespace std;
int gi(){
char ch=getchar(); int x=0,q=0;
while(ch<'0'||ch>'9') q=ch=='-'?1:q,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return q?-x:x;
}
const int M=210;
const int N=1e6+10;
const int INF=0x3f3f3f3f;
int T,n,S,E,cnt,mp[1010];
struct Matrix {
int MT[M][M];
IL void clear() {
RG int i,j;
for (i=1;i<=200;++i)
for (j=1;j<=200;++j) MT[i][j]=INF;
}
}ver,ans;
IL Matrix handle(Matrix a,Matrix b) {
RG int i,j,k;
RG Matrix now;
now.clear();
for (i=1;i<=cnt;++i)
for (j=1;j<=cnt;++j)
for (k=1;k<=cnt;++k)
now.MT[i][j]=min(now.MT[i][j],a.MT[i][k]+b.MT[k][j]);
return now;
}
IL void quick_pow(int P) {
for (--P,ans=ver;P;P>>=1,ver=handle(ver,ver))
if (P&1) ans=handle(ans,ver);
}
int main(){
RG int i,len,x,y;
n=gi(),T=gi(),S=gi(),E=gi();
ver.clear();
for (i=1;i<=T;++i) {
len=gi(),x=gi(),y=gi();
if (!mp[x]) mp[x]=++cnt;
if (!mp[y]) mp[y]=++cnt;
ver.MT[mp[x]][mp[y]]=ver.MT[mp[y]][mp[x]]=len;
}
quick_pow(n);
printf ("%d\n",ans.MT[mp[S]][mp[E]]);
return 0;
}