​题目传送门 - 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;
}