#include<algorithm>
#include<cstdio>
#include<queue>
using namespace std;
int const INF=(1<<31)-1;
int n,m,s;
struct Edge{
int nxt,to;
int val;
}e[500101];
int head[10101],tot;
queue<int> q;
int dis[10101];
bool inq[10101];
void addedge(int x,int y,int z){
e[++tot].nxt=head[x];
head[x]=tot;
e[tot].to=y;
e[tot].val=z;
}
void SPFA(int s){
for(int i=1;i<=n;i++){
dis[i]=INF;
inq[i]=false;
}
q.push(s);
dis[s]=0;inq[s]=true;
while(q.size()){
int x=q.front();q.pop();
inq[x]=false;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to,w=e[i].val;
if(dis[y]>dis[x]+w){
dis[y]=dis[x]+w;
if(!inq[y]){
q.push(y);inq[y]=true;
}
}
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=m;i++){
int x,y,z;scanf("%d%d%d",&x,&y,&z);
addedge(x,y,z);
}
SPFA(s);
for(int i=1;i<=n;i++)
printf("%d ",dis[i]);
return 0;
}
Dijkstra#include<algorithm>
#include<cstdio>
#include<queue>
using namespace std;
int const INF=0x7f7f7f7f;
int n,m,s;
struct Edge{
int nxt,to;
int val;
}e[200101];
int head[100101],tot=0;
void addedge(int x,int y,int z){
e[++tot].nxt=head[x];
head[x]=tot;
e[tot].to=y;
e[tot].val=z;
}
struct Node{
int id,dis;
bool operator <(const Node &oth) const {
return dis>oth.dis;
}
};
priority_queue<Node> q;
int dis[100101];
bool vis[100101];
void Dijkstra(int s){
for(int i=1;i<=n;i++){
dis[i]=INF;
vis[i]=false;
}
dis[s]=0;
q.push({s,0});
while(q.size()){
int x=().id;q.pop();
if(vis[x]) continue;
vis[x]=true;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to,w=e[i].val;
if((dis[y]>dis[x]+w)&&(dis[x]!=INF)){
dis[y]=dis[x]+w;
q.push({y,dis[y]});
}
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=m;i++){
int x,y,z;scanf("%d%d%d",&x,&y,&z);
addedge(x,y,z);
}
Dijkstra(s);
for(int i=1;i<=n;i++){
printf("%d ",dis[i]);
}
}

可以设计出dp[x,p]表示从1号节点到达x号节点,指定p条边免费时,经过的路径上最贵的边的最小花费是多少。对于一条边(x,y,z),用max(dp[x,p],z)更新dp[y,p]的值,用dp[x,p]更新dp[y,p+1]的值。但是由于给出的是一张无向图,已经推导出的状态可能会由后续状态再次更新(没有一个确定的顺序更新dp方程)。考虑利用迭代思想,不断利用变量当前的旧值推出新值,借助SPFA算法进行动态规划,直到所有状态都被更新完。
从最短路问题的角度去理解,图中的节点可以扩展到二维,用(x,p)代表一个节点(dp方程的一个状态),从(x,p)到(y,p)连一条长度为z的边,从(x,p)到(y,p+1)连一条长度为0的边。D[x,p]表示从起点(1,0)到节点(x,p)路径上最长的边最短是多少。
code从(x,p)这个节点走到(y,p)这个节点,z有可能更新答案,所以连一条长度为z的边。
从(x,p)这个节点走到(y,p+1)这个节点,答案是由(x,p)更新的,所以连一条长度为0的边。
每次从x节点选择走不同的路到y节点,都对应着dp方程的一个状态转移。
而要求的就是所有状态转移出来的最终状态的最小值,最短路跑出来的就是从(1,0)到(x,p)所有路径上边的最小值。最终的答案也是一样的。
#include<algorithm>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
int const maxn=1000101;
int const maxe=20000101;
struct Edge{
int nxt,to;
int val;
}e[maxe];
int head[maxn],tot;
int n,m,k;
void addedge(int x,int y,int z){
e[++tot].nxt=head[x];
head[x]=tot;
e[tot].to=y;
e[tot].val=z;
}
queue<int> q;
int dis[maxn];
bool inq[maxn];
void SPFA(){
memset(dis,0x7f,sizeof(dis));
memset(inq,false,sizeof(inq));
q.push(1);
dis[1]=0;inq[1]=true;
while(q.size()){
int x=q.front();q.pop();
inq[x]=false;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to,w=max(dis[x],e[i].val);
if(dis[y]>w){
dis[y]=w;
if(!inq[y]){
q.push(y);
inq[y]=true;
}
}
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
addedge(x,y,z);addedge(y,x,z);
for(int j=1;j<=k;j++){
addedge(x+(j-1)*n,y+j*n,0);
addedge(y+(j-1)*n,x+j*n,0);
addedge(x+j*n,y+j*n,z);
addedge(y+j*n,x+j*n,z);
}
}
for(int i=1;i<=k;i++)
addedge(i*n,(i+1)*n,0);
SPFA();
if(dis[(k+1)*n]==0x7f7f7f7f) puts("-1");
else printf("%d",dis[(k+1)*n]);
return 0;
}
\(\\\)
最优贸易从节点\(1\)开始跑SPFA,首先求出节点\(1\)到节点\(x\)的所有路径中,能够经过的权值最小的节点的权值。
建立反图,从节点\(n\)开始跑SPFA,求出节点\(n\)到节点\(x\)(正图上的节点\(x\)到节点\(n\))的所有路径中,能够经过的权值最大的节点的权值。
code#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
int n,m;
int price[100101];
struct Edge{
int nxt,to;
}e[1000101];
int head[100101],tot=0;
void addedge(int x,int y){
e[++tot].nxt=head[x];
head[x]=tot;
e[tot].to=y;
}
struct edge{
int nxt,to;
}ef[1000101];
int hf[100101],tf=0;
void addfedge(int x,int y){
ef[++tf].nxt=hf[x];
hf[x]=tf;
ef[tf].to=y;
}
int minval[100101],maxval[100101];
bool inq[100101];
void SPFAmin(){
queue<int> q;
memset(minval,0x7f,sizeof(minval));
memset(inq,false,sizeof(inq));
q.push(1);
inq[1]=true;minval[1]=price[1];
while(q.size()){
int x=q.front();q.pop();
inq[x]=false;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to,w=min(minval[x],price[y]);
if(minval[y]>w){
minval[y]=w;
if(!inq[y]){
q.push(y);
inq[y]=true;
}
}
}
}
}
void SPFAmax(){
queue<int> q;
memset(maxval,0,sizeof(maxval));
memset(inq,false,sizeof(inq));
q.push(n);
inq[n]=true;maxval[n]=price[n];
while(q.size()){
int x=q.front();q.pop();
inq[x]=false;
for(int i=hf[x];i;i=ef[i].nxt){
int y=ef[i].to,w=max(maxval[x],price[y]);
if(maxval[y]<w){
maxval[y]=w;
if(!inq[y]){
inq[y]=true;
q.push(y);
}
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&price[i]);
for(int i=1;i<=m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
if(z==1){
addedge(x,y);
addfedge(y,x);
}
else if(z==2){
addedge(x,y);addedge(y,x);
addfedge(x,y);addfedge(y,x);
}
}
SPFAmin();
SPFAmax();
// for(int i=1;i<=n;i++)
// printf("i:%d min:%d max:%d\n",i,minval[i],maxval[i]);
int ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,maxval[i]-minval[i]);
printf("%d",ans);
return 0;
}
\(\\\)
Roads and Planes G有边权为负数的边,不能跑\(\text{Dijkstra}\),据说它又卡SPFA。
观察到双向边的边权为非负,且可能形成环,考虑把每个连通块看成一个“点”,再把有向边加到图内,就得到一张有向无环图。在有向无环图上,无论边权正负,都可以按照拓扑序进行扫描,在线性时间内求出单源最短路,可以利用拓扑序的框架处理整个图。在每个连通块内跑dijkstra,实际上在块内跑Dijkstra时顺便完成了块之间的转移。
这样并不是相当于Dijkstra跑了负边。在一个块内跑Dijkstra时,如果进行的是块之间的转移,那么另一个块的点并不会进入当前块进行Dijkstra的优先队列中。所以Dijkstra实际上并没有跑负边。只是相当于普通的拓扑图上的扫描转移。
因为有边权为负的边存在,所以当无解的时候不一定就dis\(\ge\) INF,比较好的判断方法是在跑Dijkstra时,访问到这个\(x\)点时,如果dis[x]==INF,肯定是没有办法到达的。
一开始把一个连通块上所有的点都放进了堆里,如果一个点出堆的时候的dis为INF,肯定是最后才出队的,所有连通块里能更新的点应该都更新了,还是没更新它,一定说明它是不能到达的。
换到其它的最短路上,一个点都是被其它点能更新的时候才会进堆。如果它的dis是INF,说明它没进过堆,一定是不能被到达的。
\(\\\)
最短路计数由于图上的边权都是1,考虑用bfs代替SPFA,Dijsktra之类的。自环可以删除,重边可以被算作不同的路径,照样存储。第一次访问到时,记录路径长度,答案加上它父节点的答案。后面访问到这个点,如果是最短路的话,直接加上它父节点的答案。
code ```cpp #include #include #include using namespace std; int const mod=100003; int n,m; struct Edge{ int nxt,to; int val; }e[4000101]; int head[1000101],tot; void addedge(int x,int y,int z){ e[++tot].nxt=head[x]; head[x]=tot; e[tot].to=y; e[tot].val=z; } queue q; bool vis[1000101]; int dis[1000101],ans[1000101]; void bfs(){ dis[1]=0;ans[1]=1;vis[1]=true; q.push(1); while(!q.empty()){ int x=q.front();q.pop(); for(int i=head[x];i;i=e[i].nxt){ int y=e[i].to,w=e[i].val; if(!vis[y]){ dis[y]=dis[x]+w; ans[y]=(ans[y]+ans[x])%mod; vis[y]=true; q.push(y); } else if(dis[y]==dis[x]+w) ans[y]=(ans[y]+ans[x])%mod; } } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ int x,y; scanf("%d%d",&x,&y); if(x==y) continue; addedge(x,y,1); addedge(y,x,1); } bfs(); for(int i=1;i<=n;i++) printf("%d\n",ans[i]); return 0; } ```\(\\\)
通往奥格瑞玛的道路二分答案加最短路判定。
code#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int n,m,b;
struct Edge{
int nxt,to;
int val;
}e[100101];
int head[10101],tot=0;
int p[10101],c[10101];
void addedge(int x,int y,int z){
e[++tot].nxt=head[x];
head[x]=tot;
e[tot].to=y;
e[tot].val=z;
}
struct Node{
int id,dis;
bool operator <(const Node &oth) const{
return dis>oth.dis;
}
};
bool vis[10101];int dis[10101];
priority_queue<Node> pq;
bool check(int num){
if((num<p[1])||(num<p[n])) return false;
for(int i=1;i<=n;i++){
if(p[i]<=num) vis[i]=false;
else vis[i]=true;
dis[i]=0x7f7f7f7f;
}
dis[1]=0;
pq.push((Node){1,0});
while(!pq.empty()){
int x=p().id;pq.pop();
if(vis[x]) continue;
vis[x]=true;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to,w=e[i].val;
if(dis[y]>dis[x]+w){
dis[y]=dis[x]+w;
pq.push((Node){y,dis[y]});
}
}
}
if(dis[n]<=b) return true;
return false;
}
int main()
{
scanf("%d%d%d",&n,&m,&b);
for(int i=1;i<=n;i++){
scanf("%d",&p[i]);
c[i]=p[i];
}
for(int i=1;i<=m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
addedge(x,y,z);
addedge(y,x,z);
}
sort(c+1,c+1+n);
if(!check(c[n])){
printf("AFK\n");
return 0;
}
int l=1,r=n,mid;
while(l<r){
mid=(l+r)>>1;
if(check(c[mid]))
r=mid;
else l=mid+1;
}
printf("%d",c[l]);
return 0;
}
\(\\\)
牛的旅行 Cow Tours一开始读错题了,上来就想到了暴力加边删边跑最长路。后来发现是求所有点对之间最短路的最大值的最小值。最长路跑出来的不是最短路的最大值。
首先dfs标记连通块,floyd求出任意两点间的最短路,求出连通块的直径,再求出在不同牧场两点间连一条边后的直径。三者取最大值后,就是当前的答案。在所有的答案中取最小值。
code#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
double const INF=1e12;
int n;
char s[201];
struct Node{
int x,y;
double distance(const Node &oth){
return sqrt((x-oth.x)*(x-oth.x)+(y-oth.y)*(y-oth.y));
}
}p[201];
double dis[201][201];
double mxp[201];
int cnt=0,blk[201];
double zj[201];
void dfs(int x){
blk[x]=cnt;
for(int y=1;y<=n;y++){
if((!blk[y])&&(dis[x][y]<INF)){
dfs(y);
}
}
}
int main()
{
// freopen("","r",stdin);
// freopen("a.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&p[i].x,&p[i].y);
for(int i=1;i<=n;i++){
scanf("%s",s+1);
for(int j=1;j<=n;j++){
if((s[j]=='1')||(i==j)){
dis[i][j]=p[i].distance(p[j]);
}
else
dis[i][j]=INF;
}
}
// for(int i=1;i<=n;i++){
// for(int j=1;j<=n;j++)
// printf("i:%d j:%d dis:%.6lf\n",i,j,dis[i][j]);
// }
for(int i=1;i<=n;i++){
if(!blk[i]){
++cnt;
dfs(i);
}
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
}
}
// for(int i=1;i<=n;i++){
// for(int j=1;j<=n;j++)
// printf("i:%d j:%d dis:%.6lf\n",i,j,dis[i][j]);
// }
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(dis[i][j]<INF)
mxp[i]=max(mxp[i],dis[i][j]);
zj[blk[i]]=max(zj[blk[i]],mxp[i]);
}
}
// for(int i=1;i<=n;i++)
// printf("%.6lf\n",mxp[i]);
double mind=INF,maxd=0.0;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++)
if(blk[i]!=blk[j]){
maxd=max(max(zj[blk[i]],zj[blk[j]]),mxp[i]+p[i].distance(p[j])+mxp[j]);
// printf("%.6lf\n",maxd);
mind=min(mind,maxd);
}
}
printf("%.6lf",mind);
return 0;
}
\(\\\)
速度限制一开始想到了,用所有与它相连的边的速度替代没有速度限制的边的速度,然后连出所有的边后,跑最短路。
越测越不对…甚至还发了求助帖,原因在于最短路上跑到没有速度限制的边的时候可能用的是\(v\)速度的边,但是更新这条没有速度限制的边用的是\(u\)速度的边。并不符合按照原来的速度行驶。
看了题解后发现是二维的dijkstra(并不太好理解为分层图最短路),用\(dis[i][j]\)表示以\(j\)的速度到达\(i\)点时的最短路径。用\(「x,lstv」\)更新\(「y,now」\)时,如果\(nowv==0\),那么用\(lstv\)更新。如果有限速标志用\(nowv\)更新。
code#include<algorithm>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
int n,m,d;
struct Edge{
int frm,to,nxt;
double v,s;
}e[100101];
int head[305],tot=0;
int a,b;
double v,l;
void addedge(int a,int b,double v,double l){
e[++tot].nxt=head[a];
head[a]=tot;
e[tot].frm=a;
e[tot].to=b;
e[tot].v=v;
e[tot].s=l;
}
struct Node{
int id;
int spd;
}pre[305][505];
double dis[305][505];
bool vis[305][505];
queue<Node> q;
void spfa(){
memset(dis,127,sizeof(dis));
memset(pre,-1,sizeof(pre));
dis[0][70]=0;
vis[0][70]=true;
q.push((Node){0,70});
while(!q.empty()){
int x=q.front().id,lstv=q.front().spd;
q.pop();
vis[x][lstv]=false;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to,nowv=e[i].v,len=e[i].s;
if(nowv==0) nowv=lstv;
if(dis[x][lstv]+(double)(len/(nowv/1.0))<dis[y][nowv]){
pre[y][nowv].id=x,pre[y][nowv].spd=lstv;
dis[y][nowv]=dis[x][lstv]+(double)(len/(nowv/1.0));
if(!vis[y][nowv]){
vis[y][nowv]=1;
q.push((Node){y,nowv});
}
}
}
}
}
void print(int x,int v){
if(pre[x][v].id!=-1)
print(pre[x][v].id,pre[x][v].spd);
printf("%d ",x);
}
int main(){
scanf("%d%d%d",&n,&m,&d);
for(int i=1;i<=m;i++){
scanf("%d%d%lf%lf",&a,&b,&v,&l);
addedge(a,b,v,l);
}
spfa();
int ed=-1;
double min_v=1e10;
for(int i=1;i<=500;i++){
if(min_v>dis[d][i]){
min_v=dis[d][i];
ed=i;
}
}
print(d,ed);
return 0;
}
















