Codeforces Gym100783H 最短路 其他
原文链接https://www.cnblogs.com/zhouzhendong/p/CF-Gym100783H.html
题目传送门 - CF-Gym100783H
题意
给定一个 $n$ 个节点 $P$ 条带权边的无向图,有 $m$ 个特殊点。给定开始点 $X$ 和结束点 $Y$ 。
现在请你求一个 $k$ ,使得令所有边的权值都加上 $k$ 之后,$X$~$Y$ 的最短路经过且仅经过特殊点,不能有其他的最短路途中任意一个节点不是特殊点。
问满足条件的 $k$ 最大是多少。如果 $k=\infty$ ,则输出 $Infinity$ 。如果不存在这样的 $k$ 则输出 $Impossible$ 。否则输出 $k$ 的值。
$n,m\leq 1000,\ \ \ \ \ \ P\leq 10000,\ \ \ \ \ 1\leq X,Y\leq n$
题解
我们记原图为 $g1$,记仅包含特殊点和连接他们的边的图为 $g2$ 。
对于 $g1$ 和 $g2$ 我们分别求出 $X$ 走 $i$ 步到 $Y$ 的最短路(按照原来的边权)。这个可以 nm simple dp 。
我们记 $g1$ 和 $g2$ 中最多走 $i$ 步从 $S$ 到 $T$ 的最短路值分别为 $v1[i]$ 和 $v2[i]$ 。
然后我们枚举一个 $i$ 并求走最多 $i$ 步到达 $T$ 在 $g_1$ 中的最短路值为全局最短路的 $k$ 的取值范围。
这个取值范围如何求?
我们得满足走 $i$ 步是最短的。所以:
对于 $j=i$ : $v1[i]=v2[j]$
对于 $j>i$ : $v1[i]+ki\leq v1[j]+kj\Longrightarrow v1[i]-v1[j]\leq k(j-i)\Longrightarrow k\geq \cfrac{v1[i]-v1[j]}{j-i}$
对于 $j<i$ : $v1[i]+ki\leq v1[j]+kj\Longrightarrow v1[j]-v1[i]\geq k(i-j)\Longrightarrow k\leq \cfrac{v1[j]-v1[i]}{i-j}$
如果满足上述条件的 $k$ 存在,那么我们可以用满足上述条件的最大 $k$ 值来更新答案。
细心的你可能已经发现了,我这个做法忽略了可能存在的经过非特殊点的最短路的情况。
要搞定这个东西其实很简单。我们取一个略大于 $n$ 的值 : $1024$,对于每一个初始边权 $v$,如果它连接的是两个特殊点,那么令 $v^\prime =1024v$ 否则 $v^\prime = 1024v+1$ 。
这样有什么用呢?如果存在走 $j$ 步且经过非特殊点的最短路,那么 $dis \mod 1024 < j$ 。
这个相等权值的情况要特别注意。别忘了在求之前 $k$ 的取值范围中也要加上关于这个的特判,微调一下 $k$ 的取值范围。
建议提前判掉 $Infinity$ 的情况和部分 $Impossible$ 的情况。
代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int N=1005; const LL INF=1LL<<62; int n,m,S,T; int owned[N]; struct Gragh{ static const int M=20005; int cnt,x[M],y[M],nxt[M],fst[N]; LL z[M]; void clear(){ cnt=0; memset(fst,0,sizeof fst); } void add(int a,int b,int c){ y[++cnt]=b,x[cnt]=a,z[cnt]=c,nxt[cnt]=fst[a],fst[a]=cnt; } }g,g2; void Get_owned(){ memset(owned,0,sizeof owned); int m,x; scanf("%d",&m); while (m--) scanf("%d",&x),owned[x]=1; } LL v1[N],v2[N]; LL dis[N][N]; void Getv(Gragh &g,LL v[]){ for (int i=0;i<n;i++) for (int j=1;j<=n;j++) dis[i][j]=INF; dis[0][S]=0; for (int i=0;i<n-1;i++) for (int j=1;j<=g.cnt;j++) dis[i+1][g.y[j]]=min(dis[i+1][g.y[j]],dis[i][g.x[j]]+g.z[j]); for (int i=0;i<n;i++) v[i]=dis[i][T]; for (int i=1;i<n;i++) v[i]=min(v[i-1],v[i]); } bool check1(){ return v2[n-1]<INF; } bool check2(){ int p1=0,p2=0; while (v1[p1]==INF) p1++; while (v2[p2]==INF) p2++; return p1==p2&&v1[p1]==v2[p2]; } LL k1[N],k2[N]; LL solve3(){ LL ans=-1,k=0; for (int i=n-1;i>0;i--){ if (v1[i]!=v2[i]||v2[i]==INF) continue; LL Min=1,Max=INF; // v1[i]+k*i <= v1[j]+k*j // v1[i]-v1[j] <= k*(j-i) // k >= (v1[i]-v1[j]) / (j-i) for (int j=i+1;j<n;j++){ LL now=((v1[i]>>10)-(v1[j]>>10)+j-i-1)/(j-i); if ((v1[j]&1023LL)!=j&&now*(j-i)==((v1[i]>>10)-(v1[j]>>10))) now++; Min=max(Min,now); } // v1[i]+k*i <= v1[j]+k*j // k <= (v1[j]-v1[i])/ (i-j) for (int j=0;j<i;j++){ if (v1[j]==INF) continue; LL now=((v1[j]>>10)-(v1[i]>>10))/(i-j); if ((v1[j]&1023LL)!=j&&now*(i-j)==((v1[j]>>10)-(v1[i]>>10))) now--; Max=min(Max,now); } if (Min<=Max) ans=max(ans,Max); } return ans; } int main(){ while(~scanf("%d%d%d%d",&n,&m,&S,&T)){ g.clear(),g2.clear(); for (int i=1;i<=m;i++){ int a,b,c; scanf("%d%d%d",&a,&b,&c); g.add(a,b,c); g.add(b,a,c); } Get_owned(); for (int i=1;i<=m*2;i++){ g.z[i]<<=10; if (owned[g.x[i]]&&owned[g.y[i]]){ g.z[i]++; g2.add(g.x[i],g.y[i],g.z[i]); } } Getv(g,v1); Getv(g2,v2); if (!check1()){ puts("Impossible"); continue; } if (check2()){ puts("Infinity"); continue; } LL ans=solve3(); if (ans<0) puts("Impossible"); else printf("%lld\n",ans); } return 0; }