最短路康复训练
最短路
金字塔
比平常的最短路多加了个参数。
这里的路径长度计算与其他题不同,它是一条路径的长度+这条路径中最长的那条路的长度
用平常的最短路是行不通的,不能设置平常的vis数组,即(点出队列就不再更新)这个思路是错的,因为加上路径上的路的max可能就不一样了。
代码
void dijkstra(int s)
{
for(int i=1;i<=get(1,n);i++) dis[i]=inf,mx[i]=0;
dis[s]=0;
priority_queue<pii> q;
q.push({0,s});
while(q.size())
{
int u=q.top().second;
q.pop();
//if(vis[u]) continue;
//vis[u]=1;
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(dis[j]+mx[j]>dis[u]+w[i]+max(mx[u],w[i]))
{
dis[j]=dis[u]+w[i];
mx[j]=max(mx[u],w[i]);
if(!vis[j]) q.push({-dis[j],j});
}
}
}
}
最短路树
思路
先dijkstra求一下点1到各个点的距离,然后扫一遍图,把dijkstra的最短路树弄出来,求出\(cnt[i]\),有多少个点经过i到点1,如果连接(1,i),则节省的长度为\(cnt[i]*(t-dis[i])\)。
该题还有个条件,字典序,从1到n枚举点,这样就保证了字典序
代码
const int N=2e5+10,inf=1e18;
int n,m,k;
int h[N],e[N*2],ne[N*2],w[N*2],idx;
int dis[N],cnt[N],pre[N];
bool vis[N];
vector<int> g[N];//存最短路树
void add(int a,int b,int c)
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
void dijkstra(int s)
{
for(int i=1;i<=n;i++) dis[i]=inf,vis[i]=0;
dis[s]=0;
priority_queue<pii> q;
q.push({0,s});
while(q.size())
{
int u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(dis[j]>dis[u]+w[i])
{
dis[j]=dis[u]+w[i];
if(!vis[j]) q.push({-dis[j],j});
}
}
}
}
void dfs(int u,int fa)
{
for(int i=0;i<g[u].size();i++)
{
int j=g[u][i];
if(j==fa) continue;
dfs(j,u);
cnt[u]+=cnt[j];
}
}
void solve()
{
memset(h,-1,sizeof(h));
cin>>n>>m>>k;
for(int i=1;i<=n;i++) cin>>cnt[i];
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
dijkstra(1);
int ans=0;
for(int u=1;u<=n;u++)
{
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
//pre数组是如果dis[j]可以由多个点转移过来,则选最小的
if(dis[j]==dis[u]+w[i]&&!pre[j])
{
g[u].push_back(j);
pre[j]=u;
}
}
}
dfs(1,-1);
for(int i=1;i<=n;i++)
{
ans=max(ans,cnt[i]*(dis[i]-k));
}
cout<<ans<<endl;
}
通往奥格瑞玛的道路
题意
给出一个图,图有两个信息:每个城市会收钱,每条路会扣血,主角血条为K,问从1走到n,血不扣到负数的情况下,经过的城市收费最大值的最小值。
思路
二分+最短路。一开始subtask1中第三个点老是过不去,最后看题解,???我一开始就在城市1它都要收费啊?
代码
bool dijkstra(int s,int x)
{
for(int i=1;i<=n;i++) dis[i]=inf,vis[i]=0;
priority_queue<pii> q;
dis[s]=0;
q.push({0,s});
while(q.size())
{
int u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(f[j]>x) continue;
if(dis[j]>dis[u]+w[i])
{
dis[j]=dis[u]+w[i];
if(!vis[j]) q.push({-dis[j],j});
}
}
}
return dis[n]<=k;
}
void solve()
{
memset(h,-1,sizeof(h));
cin>>n>>m>>k;
for(int i=1;i<=n;i++) cin>>f[i];
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
if(a==b) continue;
add(a,b,c);
add(b,a,c);
}
int l=f[1],r=inf;//l要从城市1收的钱开始
while(l<r)
{
int mid=(l+r)/2;
if(dijkstra(1,mid)) r=mid;
else l=mid+1;
}
if(r==inf) cout<<"AFK\n";
else cout<<r<<endl;
}
P5837
题意
图中两个信息:每条管道的花费、流量,求1到n的流量与花费之比的最大值。
思路
首先花费能小就小,这就是求最短路了。
在上面的基础上增加约束条件:流量,从1到n的总流量取的是这条路中的管道的最小值,
题目说花费和流量都是1~1000的正整数,直接枚举最小流量做1000遍最短路。
好在它数据范围比较小,大点就用二分。
同类型题:P3063
上面的那题也差不多
代码
double dijkstra(int s,int x)
{
for(int i=1;i<=n;i++) dis[i]=inf,vis[i]=0;
priority_queue<pii> q;
dis[s]=0;
q.push({0,s});
while(q.size())
{
int u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(f[i]<x) continue;//x已经是最小了,别比它更小
if(dis[j]>dis[u]+w[i])
{
dis[j]=dis[u]+w[i];
if(!vis[j]) q.push({-dis[j],j});
}
}
}
if(dis[n]==inf) return -1;
return x*1.0/dis[n];
}
void solve()
{
memset(h,-1,sizeof(h));
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int a,b,c,d;
cin>>a>>b>>c>>d;
if(a==b) continue;
add(a,b,c,d);
add(b,a,c,d);
}
int ans=0;
for(int i=1;i<=1000;i++)
{
int k=dijkstra(1,i)*1e6;
ans=max(ans,k);
}
cout<<ans<<endl;
}
P2047
思路
范围只有100,处理各个点对间的问题。 floyd的拿手好戏。
求最短路的时候顺便把两个点间最短路的条数也处理一下。
之后就是根据题目要求算答案了
代码
void floyd()
{
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(dis[i][j]>dis[i][k]+dis[k][j])
{
dis[i][j]=dis[i][k]+dis[k][j];
e[i][j]=e[i][k]*e[k][j];
}
else if(dis[i][j]==dis[i][k]+dis[k][j])
{
e[i][j]+=e[i][k]*e[k][j];
}
}
}
}
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(i==j||j==k||i==k) continue;
if(e[i][j]==0) continue;
if(dis[i][j]==dis[i][k]+dis[k][j])
{
ans[k]+=1.0*(e[i][k]*e[k][j])/e[i][j];
}
}
}
}
}
void solve()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
dis[i][j]=dis[j][i]=inf;
}
}
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
dis[a][b]=dis[b][a]=min(dis[a][b],c);
e[a][b]=e[b][a]=1;
}
floyd();
for(int i=1;i<=n;i++)
{
cout<<fixed<<setprecision(3)<<ans[i]<<endl;
}
}
P2176
题意
原本有个图,问将某一条边加倍后,最短路与之前的最短路的最大差值
思路
先将原来的图求一遍最短路,然后把最短路内的所有边都记录,加倍的路就是这些路的其中一条,因为最短路外的边加倍那图的最短路也没变。
那如果原来的最短路不止一条上面的做法会不会失效?不会,假设有两条最短路,两种情况:
如果它们没有公共的边,那么选任何一条最短路的边来加倍,那之后的图最短路就是之前没改的那一条
如果它们有公共的边,那么最优解肯定选它们的公共边的某一条加倍(不一定要选公共边中最长的那条,想想为什么)
时间复杂度:n为100,原图最短路含边最多为n-1,做n-1遍最短路,
复杂度为\(O((n-1)(n+m)logm)\), \(100*(100+5000)*log_2{5000} \approx 1300*5100\)
代码
const int N=210,inf=1e18;
int n,m,k,s,t;
int e[N][N],pre[N],dis[N];
vector<int> g[N];
bool vis[N];
void dijkstra(int s,bool flag)
{
for(int i=1;i<=n;i++) dis[i]=inf,vis[i]=0;
dis[s]=0;
priority_queue<pii> q;
q.push({0,s});
while(q.size())
{
int u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=0;i<g[u].size();i++)
{
int j=g[u][i];
if(dis[j]>dis[u]+e[u][j])
{
dis[j]=dis[u]+e[u][j];
if(flag) pre[j]=u;
if(!vis[j]) q.push({-dis[j],j});
}
}
}
}
void solve()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
e[a][b]=e[b][a]=c;
g[a].push_back(b);
g[b].push_back(a);
}
dijkstra(1,1);
int k=dis[n],mx=0;
int now=n;
while(now!=1)
{
e[now][pre[now]]*=2;
e[pre[now]][now]*=2;
dijkstra(1,0);
mx=max(mx,dis[n]-k);
e[now][pre[now]]/=2;
e[pre[now]][now]/=2;
now=pre[now];
}
cout<<mx<<endl;
}
2023.4.21
这一个星期刷了下最短路的题,把有意思的题记录了一下。上个大学怎么这么多事啊,java课设、计组实验、数模论文、明天就是天梯赛了,这个星期可能是这个学期最累的一个星期了。