Elaxia的路线/洛谷P2149 题解

文章讲述了如何通过Dijkstra算法找到两个人之间的最短路径,并利用重构图和拓扑排序求解公共路径,最终实现高效计算两人之间的公共边和最长公共路径长度。
摘要由CSDN通过智能技术生成

这题很有意思,综合性也比较强。

我们发现,两个人都只会走最短路径,于是我们可以把参与点对最短路径的边标记出来(其他的边都没有影响)。显然,每个点对中因为只有参与最短路径的边,它们构成有向无环图。并且,最优答案中公共路径必然是一个连通块。这是因为,假设有两个分离的公共联通块,两条最短路径对它们的连接方式是一样的(两端相同,中间都走最短路),肯定可以走在一起,把两块连起来。我们挑出一对点重构后的图,在上面可以判断公共边,然后拓扑+dp就可以了。

首先,分别处理出两个点对的重构图。这一点可以从起点跑完最短路后从终点倒着找,通过判断边的起点的 $dis$ 值加上边权是否等于终点的 $dis$ 值 来确定此边是否可以在最短路径上,由于点数不多,可以使用两层邻接矩阵存两个点对重构的边。

随后,我们在其中一张图上按照拓扑排序dp,dp设有两个维度,假设当前在第$i$点,$j$是它的前驱:

$dp_{i0}$ 表示$i$点所在的连续公共路径长度(并不要求当前最长);

$dp_{i1}$表示起点起至第$i$最长连续公共路径长度(相当于答案);

当一条边是公共边时,
$dp_{i0}$=$\rm max$$(dp_{j0}+w)$,其中$w$是该边边权,表示第$i$点的值由第$j$点延伸;

$dp_{i1}$=$\rm max$$(dp_{i1},\rm max$$(dp_{j1},dp_{i0}))$,表示保持/用前驱的答案/用自己刚才更新的答案。

当一条边不是公共边时,
$dp_{i0}=0$,第$i$点不能参与当前公共路径;

$dp_{i1}$=$\rm max$$(dp_{i1},$$dp_{j1})$,表示保持/用前驱的答案。

终点时的$dp_{end1}$就是最后的结果了,但是要注意,两人相向而行的边也算公共边,这点不太好存,需要把另一张图方向反一反再来一遍。(显然路径中不会同时出现两人相向同向)

#include<bits/stdc++.h>
#define N 1509
#define M 600009
#define INF 0x3f3f3f3f
#define mod 998244353
using namespace std;
typedef long long ll;
typedef long double ldb;
typedef pair<int,int> pii;
int n,m,num,sum,en,xp,yp,xpp,ypp,ans;
int vis[N],head[M],to[M],val[M],nxt[M],dis[N],mp[2][N][N],flg[N][N],in[N],dp[N][2],flgg[2][N];
void add(int u,int v,int w){
  to[++num]=v,nxt[num]=head[u],head[u]=num,val[num]=w;
}
void dij(int rt){
  memset(vis,0,sizeof(vis));
  memset(dis,INF,sizeof(dis));
  priority_queue<pii,vector<pii>,greater<pii> > q;
  dis[rt]=0;
  q.push(make_pair(0,rt));
  while(!q.empty()){
    int u=q.top().second;q.pop();
    if(vis[u])continue;vis[u]=1;
    for(int i=head[u];i;i=nxt[i]){
      int v=to[i],w=val[i];
      if(dis[v]>dis[u]+w){
        dis[v]=dis[u]+w;
        if(!vis[v])q.push(make_pair(dis[v],v));
      }
    }
  }
}
void dfs(int x,int id){
  if(flgg[id][x])return;
  flgg[id][x]=1;
  for(int i=head[x];i;i=nxt[i]){
    int y=to[i],w=val[i];
    if(dis[y]+w==dis[x]){
      mp[id][y][x]+=w;//id表示边属于哪一条路径
      dfs(y,id);
    }
  }
}
void topo(){
  memset(in,0,sizeof(in));
  memset(dp,0,sizeof(dp));
  queue<int> q;q.push(xp);
  for(int i=1;i<=n;i++){
    for(int j=1;j<=n;j++){
      if(mp[0][i][j])in[j]++;
    }
  }
  while(!q.empty()){
    int u=q.front();q.pop();
    for(int v=1;v<=n;v++){
      if(mp[0][u][v]){
        if(mp[1][u][v]){//如果这是公共边
          dp[v][0]=max(dp[v][0],dp[u][0]+mp[0][u][v]);//公共路径从前驱节点延伸
          dp[v][1]=max(dp[v][1],max(dp[u][1],dp[v][0]));//更新答案,连不连这条边
        }else{//不是公共边
          dp[v][1]=max(dp[v][1],dp[u][1]);//从前驱节点继承
          dp[v][0]=0;//a不能参与公共路径
        }
        in[v]--;
        if(!in[v])q.push(v);
      }
    }
  }
  ans=max(ans,dp[yp][1]);
}
int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  cin>>n>>m;
  cin>>xp>>yp>>xpp>>ypp;
  for(int i=1;i<=m;i++){
    int u,v,w;
    cin>>u>>v>>w;
    add(u,v,w);
    add(v,u,w);
  }
  dij(xp);dfs(yp,0);
  dij(xpp);dfs(ypp,1);
  topo();
  for(int i=1;i<=n;i++){
    for(int j=1;j<=n;j++){
      if(mp[1][i][j]&&!(flg[i][j]||flg[j][i])){swap(mp[1][i][j],mp[1][j][i]);flg[i][j]++,flg[j][i]++;}//另一条路径反向
    }
  }
  topo();
  cout<<ans;
  return 0;
}

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值