Codeforces Round #621 (Div. 1 + Div. 2) D

题意

一张图, k k k个特殊点。
必须在特殊点之间加一条边,但不确定是哪对,使得最短路最大。

题解

首先,求出每个点到 1 1 1和到 n n n的最短路。
容易发现加边之后能得到的最短路为:
m i n ( d 1 [ a ] + 1 + d 2 [ b ] , d 1 [ b ] + 1 + d 2 [ a ] , d [ 1 ] [ n ] ) min(d_1[a]+1+d_2[b],d_1[b]+1+d_2[a],d[1][n]) min(d1[a]+1+d2[b]d1[b]+1+d2[a],d[1][n])

我们如何维护呢?首先我们不确定前两项的大小,这是关键。
昨晚在这里思路走歪了,虽然还是给我 A C AC AC了。
我列式子:
如果已知最短路 x x x,我肯定只要找到一对关键点,使得下面等式成立,
那么他们的最小值也会大于等于这个最短路,也就是说 x x x可以作为一个趋向于正确的解。也就是二分。
d 1 [ a ] + 1 + d 2 [ b ] > = x d_1[a]+1+d_2[b]>=x d1[a]+1+d2[b]>=x
d 1 [ b ] + 1 + d 2 [ a ] > = x d_1[b]+1+d_2[a]>=x d1[b]+1+d2[a]>=x
怎么快速找呢?
d 2 [ b ] > = x − 1 − d 1 [ a ] d_2[b]>=x-1-d_1[a] d2[b]>=x1d1[a]
d 1 [ b ] > = x − 1 − d 2 [ a ] d_1[b]>=x-1-d_2[a] d1[b]>=x1d2[a]
显然枚举 a a a,然后找到一个点满足这个式子,显然的二维线段树。
但是复杂度不成。
可以发现用离线树状数组可以,只需要注意到去除自己即可。
(如果自己和自己满足的话)

#include
#define FOR(i,l,r) for(int i=l;i<=r;i++)
using namespace std;
 
//typedef long long ll;
 
const int maxn=5e5+500,inf=0x3f3f3f3f;
const int maxm=1e6+600;
 
struct Edge{
    int from,to;int dist;
    Edge(){}
    Edge(int _from,int _to,int _dist):from(_from),to(_to),dist(_dist){}
};
Edge ed[maxm];int he[maxn],ne[maxm],etop=1;
void insert(int u,int v,int w){ed[etop]=Edge(u,v,w);ne[etop]=he[u];he[u]=etop++;}
 
struct node{
    int s,t,id,who;
    friend bool operator < (node a,node b){
        if(a.s==b.s&&a.t==b.t)return a.id>b.id;
        if(a.s==b.s)return a.t<b.t;
        return a.s<b.s;
    }
}A[maxn],B[maxn];
 
int n,m,k;
int a[maxn];
int d[maxn];
bool vis[maxn];
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;
void dijkstra(int s){
    memset(vis,0,sizeof(vis));
    memset(d, 0x3f, sizeof(d));
    d[s] = 0;
    q.push(make_pair(0, s));
    while(!q.empty()){
        int now = q.top().second;q.pop();
        if(!vis[now]){
            vis[now] = true;
            for(int i = he[now]; i; i = ne[i]){
                Edge& e = ed[i];
                if(d[e.to] > d[now] + e.dist){
                    d[e.to] = d[now] + e.dist;
                    q.push(make_pair(d[e.to], e.to));
                }
            }
        }
    }
}
 
int C[500050];
const int limit=500005;
int lowbit(int x){return (x&(-x));}
void change(int x,int d){for(int i=x;i<=limit;i+=lowbit(i)){C[i]+=d;}}
int query(int x){if(x<=0)return 0;int ret=0;for(int i=x;i;i-=lowbit(i)){ret+=C[i];}return ret;}
int suf[maxn];
 
bool check(int x){
    memset(C,0,sizeof(C));
    for(int i=0;i<=2*n+100;i++)suf[i]=0;
    for(int i=1;i<=k;i++){
        B[i].id=0;
        B[i].s=A[i].s,B[i].t=A[i].t;
        B[i+k].id=1;
        B[i+k].s=x-B[i].t-1,B[i+k].t=x-B[i].s-1;
        B[i].s+=n+1,B[i].t+=n+1,B[i+k].s+=n+1,B[i+k].t+=n+1;
        B[i].who=i,B[i+k].who=i;
        if(A[i].s+A[i].t+1>=x)suf[i]=1;
    }
    //for(int i=1;i<=2*k;i++)cout<
    sort(B+1,B+1+2*k);
  //  for(int i=1;i<=2*k;i++)cout<
    for(int i=2*k;i>=1;i--){
        if(B[i].id){
            int ret=query(limit)-query(B[i].t-1);
            if(ret-suf[B[i].who])return true;
        }
        else change(B[i].t,1);
    }
    return false;
}
 
int main(){
    cin>>n>>m>>k;
    FOR(i,1,k)scanf("%d",&a[i]);
    FOR(i,1,m){
        int u,v;scanf("%d%d",&u,&v);
        insert(u,v,1);
        insert(v,u,1);
    }
    int len;
    dijkstra(1);len=d[n];
    FOR(i,1,k)A[i].s=d[a[i]];
    dijkstra(n);
    FOR(i,1,k)A[i].t=d[a[i]];
    int l=0,r=len,ans;
   // FOR(i,1,k)cout<
  //  cout<
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid)){
            ans=mid;
            l=mid+1;
        }
        else r=mid-1;
    }
    cout<<ans<<endl;
}

然后就是正解。
实际上在最开始我们可以发现,怎么判断两者的大小呢 ? ? ?
d 1 [ a ] + d 2 [ b ] + 1 ≤ d 1 [ b ] + d 2 [ a ] + 1 d_1[a]+d_2[b]+1\leq d_1[b]+d_2[a]+1 d1[a]+d2[b]+1d1[b]+d2[a]+1
d 1 [ a ] − d 2 [ a ] ≤ d 1 [ b ] − d 2 [ b ] d_1[a]-d_2[a]\leq d_1[b]-d_2[b] d1[a]d2[a]d1[b]d2[b]
对于这样的 a 、 b a、b ab,显然左边式子是最小的,对于某个 a a a,所有满足差值比他小的,都是取左式,所以维护前缀最大值即可。
当然,对于一个点对,总是一个大一个小,只考虑大的那个,维护小的最大值即可。

    int len;
    dijkstra(1);len=d[n];
    FOR(i,1,k)A[i].s=d[a[i]];
    dijkstra(n);
    FOR(i,1,k)A[i].t=d[a[i]];
    sort(A+1,A+1+k);
    int ans=0,mx=A[1].s;
    for(int i=2;i<=k;i++){
        ans=max(ans,mx+A[i].t+1);
        mx=max(mx,A[i].s);
    }
    ans=min(ans,len);
    cout<<ans<<endl;

你可能感兴趣的:(coderforce)