[Problem Discription] \color{blue}{\texttt{[Problem Discription]}} [Problem Discription]
有一个由 n n n 个点和 m m m 条边组成的无向图,每条边的长度都是 1 1 1。
初始时,每条边限速为 1 1 1。你有 k k k 次操作,每次操作可以使某一条边的限速加 1 1 1。 k k k 次操作后,如果一条边的最终限速为 a a a,那么你需要 1 a \dfrac{1}{a} a1 的时间通过这条边。
求 k k k 次操作后从 s 1 s_{1} s1 到 t 1 t_{1} t1 和从 s 2 s_{2} s2 到 t 2 t_{2} t2 的总时间最小。求出这个最小总时间。
1 ≤ n , m ≤ 5000 , 1 ≤ k ≤ 1 × 1 0 9 1 \leq n,m \leq 5000,1 \leq k \leq 1 \times 10^{9} 1≤n,m≤5000,1≤k≤1×109。
[Analysis] \color{blue}{\texttt{[Analysis]}} [Analysis]
首先,我们要发现这样一个性质:从 s 1 s_{1} s1 到 t 1 t_{1} t1 的路径(记为 s 1 → t 1 s_{1} \to t_{1} s1→t1)和从 s 2 s_{2} s2 到 t 2 t_{2} t2 的路径(记为 s 2 → t 2 s_{2} \to t_{2} s2→t2)如果有公共部分,则公共部分一定连续。
证明使用反证法:如果公共部分不连续,则从第一段公共部分的最后一个点到第二段公共部分的第一个点在两条路径中选择了不同的方案。
- 如果两个方案的耗时不相同,我们可以让耗时较多的那一次也走耗时较少的路径,总时间一定会更小,矛盾。
- 如果两个方案的耗时相同,为什么非要走不同的路径呢?况且我们可以把对两条路径的操作合并起来对同一段路径操作,这样总时间一定会更小。
可以枚举公共部分的起点和终点,分别记为 u , v u,v u,v。我们发现公共部分的每条边的地位是相等的,非公共部分的每条边的地位也是相等的。即:具体要走怎样的路径对结果没有影响,有影响的是公共部分的总边数 share \text{share} share 和非公共部分的总边数 individual \text{individual} individual。
假设对公共部分进行 mid \text{mid} mid 次操作,可以证明这 mid \text{mid} mid 次操作应该尽量平均分在 share \text{share} share 条边上,那么单次行程在公共部分的耗时应该是:
∑ i = 1 mid m o d share 1 ⌊ mid share ⌋ + 2 + ∑ i = 1 share − ( mid m o d share ) 1 ⌊ mid share ⌋ + 1 \sum\limits_{i=1}^{\text{mid} \mod \text{share}} \dfrac{1}{ \left \lfloor \frac{\text{mid}}{\text{share}} \right \rfloor +2}+\sum\limits_{i=1}^{\text{share}-(\text{mid} \mod \text{share})} \dfrac{1}{\lfloor \frac{\text{mid}}{\text{share}} \rfloor +1} i=1∑midmodshare⌊sharemid⌋+21+i=1∑share−(midmodshare)⌊sharemid⌋+11
mid \text{mid} mid 可以用三分求出。
时间复杂度 O ( n 2 + n log k ) O \left (n^{2}+n \log k \right ) O(n2+nlogk)。
Code \color{blue}{\text{Code}} Code
int n,m,k;
int dis[N][N];
queue<int> q;
inline void bfs(int s){
for(int i=1;i<=n;i++)
dis[s][i]=(i==s?0:inf);
q.push(s);
while (!q.empty()){
int u=q.front();q.pop();
for(int i=h[u];i;i=e[i].nxt){
int v=e[i].to;
if (dis[s][v]==inf){
dis[s][v]=dis[s][u]+1;
q.push(v);
}
}
}
}//边权相同,可以用 bfs 求最短路
double calc(int tot,int val){
if (tot==0) return 0;
if (val==0) return 1.0*tot;
int ave=val/tot,lef=val%tot;
return (1.0*(tot-lef)/(ave+1)+1.0*lef/(ave+2));
}//对 tot 条路可以操作 val 次最小时间
double check(int sha,int idv,int mid){
return 2.0*calc(sha,mid)+calc(idv,k-mid);
}//别忘了乘以 2
double solve(int sha,int idv){
int l=0,r=k;
double ret=1e30;
while (l<=r){
int lmid=l+(r-l)/3,rmid=r-(r-l)/3;
if (check(sha,idv,lmid)<check(sha,idv,rmid)){
r=rmid-1;
ckmin(ret,check(sha,idv,lmid));
}
else{
l=lmid+1;
ckmin(ret,check(sha,idv,rmid));
}
}
return ret;
}
int path[N],s1,t1,s2,t2;
double ans;
int main(){
scanf("%d%d%d",&n,&m,&k);
for(int i=1,u,v;i<=m;i++){
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
scanf("%d%d%d%d",&s1,&t1,&s2,&t2);
for(int i=1;i<=n;i++) bfs(i);
for(int i=0;i<=n;i++) path[i]=inf;
for(int u=1;u<=n;u++)
for(int v=1;v<=n;v++)
if (dis[u][v]!=inf){
if (dis[s1][u]==inf||dis[s2][u]==inf) continue;
if (dis[v][t1]==inf||dis[v][t2]==inf) continue;
ckmin(path[dis[u][v]],dis[s1][u]+dis[s2][u]+dis[v][t1]+dis[v][t2]);
if (dis[t2][u]==inf||dis[v][s2]==inf) continue;
ckmin(path[dis[u][v]],dis[s1][u]+dis[t2][u]+dis[v][t1]+dis[v][s2]);
}
ans=check(0,dis[s1][t1]+dis[s2][t2],0);
for(int i=0;i<=n;i++)
if (path[i]!=inf)
ckmin(ans,solve(i,path[i]));
printf("%.20lf",ans);
return 0;
}
h,e 是链式前向星使用的数组
ckmin(a,b) 表示让 a 取得 a,b 中的较小值