目录
今天知识点
求到点的最大边权最小直接dijkstra,人家本来就是按topo序
求来回最短路径,正向反向建边跑
当权值变小时候,可以在原floyd的图上单独跑权值为0的边
判断负环,判断最短路长度即可
询问第t天的最短路,就每天更新一条边
题目:营救
思路:
(一道dijkstral的变种题)
dis[v]表示从s到v点的最大拥挤度,那么每个通向v的点u有:
dis[v]=min(max(dis[u],uv_w))
采用dijkstra思想(dp思想)来做就是:
【选点】:原则上是最小的dis点。因为队列中其余的点dis本来就更大(不考虑旧白点),权值取max后不可能超过该点的dis。故此点已经确定不会再被更新。
【更新未确定点】:松弛操作 dis[v]>max(dis[u],w)时 dis[v]=max(dis[u],w)
#include <bits/stdc++.h>//1396 求从s到t的最大拥挤度的最小值
using namespace std;
typedef pair<int ,int> pii;
const int maxn=1e4+5,maxm=3e4+5;
int n,m,s,t,w,tot,head[maxn];
int dis[maxn];
bool vis[maxn];
struct node {int to,w,next;}e[maxm];
void add(int u,int v,int w){e[++tot]=(node){v,w,head[u]};head[u]=tot;}
void dijkstra(int s){
priority_queue<pii,vector<pii>,greater<pii> >q;
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
q.push(make_pair(dis[s],s));
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].next){
int v=e[i].to,w=e[i].w;
if(vis[v])continue;//如果u比v大,那么v在确定的时候就一定会借助u点,所以不成立,这句话加不加都一样
if(dis[v]>max(dis[u],w)){
dis[v]=max(dis[u],w);
q.push(make_pair(dis[v],v));
}
}
// cout<<"u: "<<u<<'\n';
// for(int i=1;i<=n;i++)cout<<i<<' '<<dis[i]<<'\n';
}
}
int main(){
tot=0;
memset(head,0,sizeof(head));
scanf("%d%d%d%d",&n,&m,&s,&t);
int u,v,w;
for(int i=1;i<=m;i++){
scanf("%d %d %d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
dijkstra(s);
cout<<dis[t];
}
题目:POJ3268:
每头牛都可以从任何一个农场N去参加X点的聚会,一个有M个单行道,对应花费Ti时间,参加完聚会后每头牛都要回到原来的地方。它们来回都直走耗时最短的路径,求所有牛中参加聚会来回的最长时间。
输入:
4 8 2
1 2 4
1 3 2
1 4 7
2 1 1
2 3 5
3 1 2
3 4 4
4 2 3
思路
等价与求一个点到每个点的来回的最短距离。那么先从x跑一次最短路相当于回去的路径,因为去的路径也是最短路径且只到x点,那么我们反向建边
就相当于从x开始再跑一次最短路,然后两次的加在一起求最大值即可。
还不理解的话可以想象一下:原图中从a到b的某一路径,就是反向建图中的b到a同一路径,那么当a到b的这一路径恰好是最短路径时候,那么反向图中的b到a的这一路径也一定是同一路径,且是最短路径。
#include <bits/stdc++.h>
using namespace std;
const int maxn=10005,maxm=100005,INF=0x3f3f3f3f;
int n,m,x,tot,tt;
int head[maxn],hh[maxn],dis[maxn],dd[maxn];
bool vis[maxn];
struct node{int to,next,w;}e[maxm],ee[maxm];
void add1(int u,int v,int w){e[++tot]=(node){v,head[u],w};head[u]=tot;}
void add2(int u,int v,int w){ee[++tt]=(node){v,hh[u],w};hh[u]=tt;}
void spfa(int t){
queue<int> q;
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
dis[t]=0; vis[t]=1; //注意vis等于1表示队列中已经存在此点
q.push(t);
while(!q.empty()){
int cur=q.front(); q.pop();
vis[cur]=0;//扩展后此点出队
for(int i=head[cur];i;i=e[i].next){//i是边号 遍历点cur连向的周围边i的点v
int v=e[i].to,w=e[i].w;
if(dis[v]>dis[cur]+w){//判断是否需要更新,更新过的且不在队伍的点才入队,方便找更优解
dis[v]=dis[cur]+w;
if(!vis[v])q.push(v),vis[v]=1;
}
}
}
}
void spfa2(int t){
queue<int> q2;
memset(vis,0,sizeof(vis));
memset(dd,0x3f,sizeof(dd));
dd[t]=0; vis[t]=1; //注意vis等于1表示队列中已经存在此点
q2.push(t);
while(!q2.empty()){
int cur=q2.front(); q2.pop();
vis[cur]=0;//扩展后此点出队
for(int i=hh[cur];i;i=ee[i].next){//i是边号 遍历点cur连向的周围边i的点v
int v=ee[i].to,w=ee[i].w;
if(dd[v]>dd[cur]+w){//判断是否需要更新,更新过的且不在队伍的点才入队,方便找更优解
dd[v]=dd[cur]+w;
if(!vis[v])q2.push(v),vis[v]=1;
}
}
}
}
int main(){
cin>>n>>m>>x;
int u,v,w;
for(int i=1;i<=m;i++){
cin>>u>>v>>w;
add1(u,v,w);
add2(v,u,w);
}
spfa(x);
spfa2(x);
long long ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,1ll*dis[i]+dd[i]);
cout<<ans<<'\n';
}
题目:传送门
思路:
先跑一边floyd,然后依次加入每个传送门,O(n^5)不行。
所以不能跑n^2次floyd,应该单独把两个有影响的点摘出来处理dis,就能降为O(n^4)。
为什么?为什么可以单独拿出来?
你先这样想一下刚开始在经过i点中转时候,绝大多数点k1对应的f[k1][k2]都是不会的,但是j点的一定会变化(因为f1[k1][i]+f1[i][j]必然变小),那么在进行j点中转的时候但凡之前经过j点从而得出最优解的点的f都会变化,这个时候才把w_ij的权值影响到了整个图。
#include <bits/stdc++.h>
using namespace std;
const int N=105;
int n,m,f1[N][N],f2[N][N];
int main(){
cin>>n>>m;int u,v,w,ans=1e9;
memset(f1,0x3f,sizeof(f1));
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
f1[u][v]=w;f1[v][u]=w;
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
f1[i][j]=min(f1[i][k]+f1[k][j],f1[i][j]);
f2[i][j]=f1[i][j];//f2保存最开始跑图结果
}
for(int i=1;i<=n;i++)
for(int j=1;j<i;j++){//枚举每个传送门
f1[i][j]=f1[j][i]=0;
for(int k1=1;k1<=n;k1++)
for(int k2=1;k2<=n;k2++)
f1[k1][k2]=min(f1[k1][k2],f1[k1][i]+f1[i][k2]);//由i点进行中转
for(int k1=1;k1<=n;k1++)
for(int k2=1;k2<=n;k2++)
f1[k1][k2]=min(f1[k1][k2],f1[k1][j]+f1[j][k2]);//由i,j点进行中转
int tmp=0;
for(int k1=1;k1<=n;k1++)//这里因为我们没有初始化对角线,所以不要加对角线元素
for(int k2=1;k2<k1;k2++)//把根据无向图的对称型即可
tmp+=f1[k1][k2];
ans=min(ans,tmp);
memcpy(f1,f2,sizeof(f2));
}
cout<<ans;
return 0;
}
思考一下:如果是把某一条路给拆掉,这个题还能这样做吗?
这道题在原图的跑出的floyd结果基础上对两个点间权值修改一下,这就相当于在原图上直接把两点权值给修改了这个过程是等价的。我直接这么说吧:这个修改后的权值只要比原图的两点间权值小,那么直接拿出这两个修改后的点去跑O(n^2)floyd一定都是没有问题的。但是如果修改后的权值更大,那就老老实实O(n^3)的跑floyd吧,因为这个时候就不能使用原来跑出来的结果了!!!
题目:负环判断
思路:
只需要记录最短路长度即可,这个长度不是带权的长度,是经过的点个数 如果长度大于n就有问题,也就是出现了负环,如果不停止就会走向无穷小
#include <bits/stdc++.h>
using namespace std;
const int N=2005,M=3005;
int n,m,tot;
queue<int>q;
int head[N],vis[N],dis[N],cnt[N];//dis存放到每个点的最短距离,cnt存放对应的长度
struct node{int to;int w;int next;}e[M*2];
void add(int u,int v,int w){e[++tot]=(node){v,w,head[u]};head[u]=tot;}
int spfa(){//判断负环的spfa
memset(dis,0x3f,sizeof(dis));
memset(cnt,0,sizeof(cnt));
memset(vis,0,sizeof(vis));
while(!q.empty()) q.pop();//要把队列清空(c++不支持队列清空函数)
dis[1]=0;vis[1]=1;q.push(1);
while(!q.empty()){
int cur=q.front();q.pop();
vis[cur]=0;
for(int i=head[cur];i;i=e[i].next){
int v=e[i].to,w=e[i].w;
if(dis[cur]+w<dis[v]){
dis[v]=dis[cur]+w;
cnt[v]=cnt[cur]+1//记录最短路长度;
if(cnt[v]>n)return 1;//如果长度大于n一定有问题,也就是出现了负环,如果不停止就会走向无穷小
if(!vis[v])q.push(v),vis[v]=1;
}
}
}
return 0;
}
int main(){
int t,u,v,w;
cin>>t;
while(t--){
tot=0;memset(head,0,sizeof(head));
cin>>n>>m;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
if(w>=0)add(v,u,w);
}
if(spfa())cout<<"YES"<<'\n';
else cout<<"NO"<<'\n';
}
}
题目:灾后重建
思路:
本题的本质就是逐渐加入每个点,或者说是点的修建。
floyd的最外层k其实是在放入前k个点后的对dis的影响
eg:k为1是仅放入1对dis的影响,k为2是仅放入1和2(也就是加入了边12)后对dis的影响,依次类推
那么此题,我们只需要放一个对应的点,就输出一次,这就是前k个点的影响
#include <bits/stdc++.h>
using namespace std;
const int N=205;
int n,m,INF;
int a[N],f[N][N];
inline void updata(int k){//依次放入k,产生各边从而更新整个dis
for(int i=0;i<n;i++)
for(int j=0;j<n;j++){
if(f[i][j]>f[i][k]+f[k][j])
f[i][j]=f[i][k]+f[k][j];
}
}
int main(){
cin>>n>>m;
for(int i=0;i<n;i++) cin>>a[i];//每个村庄的建好时间
memset(f,0x3f,sizeof(f));INF=f[1][1];//初始化f
for(int i=0;i<n;i++) f[i][i]=0;//初始化对角线
int u,v,w;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
f[u][v]=f[v][u]=w;
}
int q,t,now=0;cin>>q;
while(q--){
cin>>u>>v>>t;//询问
while(a[now]<=t&&now<n){//把t时间之前的村庄都考虑进去
updata(now);now++;
}
if(a[u]>t||a[v]>t)cout<<-1<<'\n';//村庄还没修好
else{
if(f[u][v]==INF)cout<<-1<<'\n';//根本就无法到达
else cout<<f[u][v]<<'\n';
}
}
return 0;
}