题解:P13614 [IOI 2018] highway 高速公路收费
前言
看刘海峰老师的讲座来做的这道题。
前置知识:最短路,二分,广度优先搜索 (bfs)。
解析
分三步骤拆解问题:
一
对于 \(A,B\) 权值做一步转化,初始时先把所有边权设为 \(A\),得到一个初始的最短路大小 \(D\),那么如果将一条边变为 \(B\),可以等效为把这条边断开(即设为 \(+\infty\)),因为它们的效果都是会使最短路的数值变大。
那么,注意到 \(S,T\) 在最短路上废话,想找到 \(D\) 对应哪一条最短路是困难的,但是得到一条必经边是容易的。考虑在边的编号上二分,找到一个最大的前缀使得删除这些边不会改变最短路。把下一条边记为 \((u,v)\),\((u,v)\) 显然就是一条必经边。
二
得到了一条必经边有什么用?
考虑用这条边把原图分为两个子图,使得分别可以求解 \(S,T\),毕竟 \(S,T\) 强相关,同时求不太现实,考虑 \(S,T\) 在某些性质上的区别。
假设有一条最短路为 \(S\to u\to v\to T\),那么 \(dis_{S,u}<dis_{S,v}\),\(dis_{T,u}>dis_{T,v}\)。
由此,分别跑 \(u,v\) 为源点的最短路,通过 \(S,T\) 性质的不同,把所有点分为两个集合 \(U,V\),一个距离 \(u\) 更近,一个距离 \(v\) 更近。
三
此时,分别在两个集合中操作。以 \(U\) 为例,求出该集合从 \(u\) 出发的 \(\operatorname{bfs}\) 序,那么考虑在上面二分,每次删除一个后缀的点,即删除它们的所有连边,然后看最短路是否发生变化,就可以二分出其中一个点。
时间复杂度 \(O(m\log m+n\log n)\),此处忽略了交互库运行时间。
询问次数为三次二分的总和,即 \(\log m+2\log n+1\)。
由于 \(2\log n\) 中 \(n\) 是不满的,所以可以通过。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll ask(const vector<int> &w);
void answer(int s, int t);
const int N=90005,M=130005;
const ll inf=1e15;
typedef pair<ll,int> PLI;
bool vis[N];
int pos,m,A,B,h[N],idx=1,e[M<<1],ne[M<<1],val[M<<1],id[N],p[2][N],sz[2];
ll res,d[2][N];
inline void add(int a,int b,int c){e[idx]=b,ne[idx]=h[a],val[idx]=c,h[a]=idx++;}
inline void solve1(int n){
vector <int> w;
int l=0,r=m-1,mid,ans=0;
while (l<=r){
mid=l+r>>1,w.clear();
for (int i=0;i<=mid;i++) w.push_back(1);
for (int i=mid+1;i<=m-1;i++) w.push_back(0);
if (ask(w)!=res) r=mid-1,ans=mid;
else l=mid+1;
}
pos=ans;
}
inline void dijkstra(int n,int s,ll d[]){
priority_queue <PLI,vector <PLI>,greater<PLI>> q;
for (int i=0;i<n;i++) d[i]=inf,vis[i]=0;
d[s]=0,q.push({d[s],s});
while (!q.empty()){
int u=q.top().second;q.pop();
if (vis[u]) continue;
vis[u]=1;
for (int i=h[u];i;i=ne[i]){
int v=e[i],w=val[i];
if (d[v]>d[u]+w)
d[v]=d[u]+w,q.push({d[v],v});
}
}
}
inline void bfs(int s,int o,int p[],int& sz){
queue <int> q;
memset(vis,0,sizeof vis);
q.push(s),vis[s]=1,p[++sz]=s;
while (q.size()){
int u=q.front();q.pop();
for (int i=h[u];i;i=ne[i]){
int v=e[i];
if (id[v]!=o||vis[v]||val[i]==B) continue;
vis[v]=1,p[++sz]=v,q.push(v);
}
}
}
inline void solve2(int n,vector <int> U,vector <int> V){
for (int i=0;i<pos;i++) add(U[i],V[i],B),add(V[i],U[i],B);
for (int i=pos;i<m;i++) add(U[i],V[i],A),add(V[i],U[i],A);
int x=U[pos],y=V[pos];
dijkstra(n,x,d[0]),dijkstra(n,y,d[1]);
for (int i=0;i<n;i++) id[i]=(d[0][i]>d[1][i]);
bfs(x,0,p[0],sz[0]),bfs(y,1,p[1],sz[1]);
}
bool st[N];
inline int solve3(int p[],int sz,vector <int> U,vector <int> V){
int l=1,r=sz,mid,ans=0;
vector <int> W;
for (int i=0;i<pos;i++) W.push_back(1);
for (int i=pos;i<m;i++) W.push_back(0);
while (l<=r){
mid=l+r>>1;
vector <int> w=W;
for (int j=mid;j<=sz;j++) st[p[j]]=1;
for (int j=0;j<m;j++)
if (st[U[j]]||st[V[j]]) w[j]=1;
for (int j=mid;j<=sz;j++) st[p[j]]=0;
if (ask(w)==res) r=mid-1;
else ans=mid,l=mid+1;
}
return p[ans];
}
void find_pair(int n, vector<int> U,vector <int> V,int _A,int _B){
vector <int> w;m=U.size();A=_A,B=_B;
for (int i=0;i<m;i++) w.push_back(0);
res=ask(w),solve1(n),solve2(n,U,V);
int S=solve3(p[0],sz[0],U,V),T=solve3(p[1],sz[1],U,V);
answer(S,T);
}

浙公网安备 33010602011771号