【POI2014】Rally(拓扑序+线段树)

题意

    给出一个n个点m条边的DAG,求删除哪一个点及其所有边使剩余图中的最长路径最短,并求出最短路径。

数据范围

    n<=75000,m<=100000

思路

    拓扑排序+线段树
    一张图可能存在连通块,于是对于每个点增设类似网络流的源汇点S,T,问题转化为删点后S到T的最长路。
    对于每个点i计算点S到点i的最长距离f[i],点i到点T的最长距离g[i],对于一条边(u,v),它所在最长路即为f[u]+1+g[v],记为该边的路径长。
    用权值线段树维护删除每个点之后剩余图的最长路径。首先将每个点的g[i]插入线段树。接着按照拓扑序删除点。删除点i时,在线段树中删除点i所有入边的路径长,以及g[i]。此时剩余图的最长路径即线段树中的最大值。继续删除点i+1之前,将f[i],以及i的所有出边的路径长加入线段树。
    此思路的关键在于拓扑序。之所以能够保证删除点i后线段树中不存在与点i相关的路径,是对于因为拓扑序小于i的点j,若其与i直接相连,则计算前被删除,若间接相连,则j可能包含点i的最长路在删除j时已经被删除;对于拓扑序大于i的点j,线段树中仅保存了g[j],不经过i。

Code

#include
#include
#include

const int sm = 75000 + 53;
const int sn = 25e4 + 53;
const int Inf = 0x3f3f3f3f;

int n,m,S,T,tot,cnt,lim;

int deg[sm],_deg[sm];

bool ex[sm];
int to[sn],nxt[sn],hd[sm],c[sn];
int _to[sn],_nxt[sn],_hd[sm],_c[sn];

int f[sm],g[sm];

int Min(int x,int y) { return xint Max(int x,int y) { return x>y?x:y; }

void Add(int u,int v,int w) {
    to[++tot] = v, nxt[tot] = hd[u], hd[u] = tot;
    c[tot] = w, deg[v]++;
}
void Ins(int u,int v,int w) {
    _to[++cnt] = v, _nxt[cnt] = _hd[u], _hd[u] = cnt;
    _c[cnt] = w, _deg[v]++;
}

int top,Stk[sm];
void Topb() {
    for(int i = 1; i <= T; ++i) g[i] = -Inf;
    Stk[++top] = T, g[T] = 0;   
    int t, j;
    while(top) {
        t = Stk[top--];
        for(int i = _hd[t]; i; i = _nxt[i]) {
            j = _to[i], --_deg[j];
            g[j] = Max(g[j],g[t]+_c[i]);
            if(_deg[j] == 0)
                Stk[++top] = j;
        }
    }
}
int sort[sm];
void Topa() {
    for(int i = 1; i <= T; ++i) f[i] = -Inf;
    Stk[++top] = S, f[S] = 0;
    int t;
    while(top) {
        sort[++sort[0]] = t = Stk[top--]; 
        for(int i = hd[t]; i; i = nxt[i]) {
            --deg[to[i]];
            f[to[i]] = Max(f[to[i]],f[t] + c[i]);
            if(deg[to[i]] == 0) 
                Stk[++top] = to[i];
        }
    } 
}

namespace ST{
    int sum[sm<<2];

    void Insert(int rt,int l,int r,int k) {
        ++sum[rt];
        if(l==r) return;
        int m = (l + r) >> 1;
        if(k <= m) Insert(rt<<1,l,m,k);
        else Insert(rt<<1|1,m+1,r,k);
    }
    void Delete(int rt,int l,int r,int k) {
        --sum[rt];
        if(l==r) return;
        int m = (l + r) >> 1;
        if(k <= m) Delete(rt<<1,l,m,k);
        else Delete(rt<<1|1,m+1,r,k);
    }
    int Query(int rt,int l,int r) {
        if(l == r) return l;
        int m = (l + r) >> 1;
        if(sum[rt<<1|1] > 0) return Query(rt<<1|1,m+1,r);
        return Query(rt<<1,l,m);
    }
}

int main() {
    freopen("chronosphere.in","r",stdin);
    freopen("chronosphere.out","w",stdout);

    int u,v;
    scanf("%d%d",&n,&m);
    S = n + 1, T = S + 1, lim = n + 5;
    for(int i = 1; i <= m; ++i) {
        scanf("%d%d",&u,&v);
        Add(u,v,1), Ins(v,u,1);
        ex[u] = ex[v] = 1;
    }
    for(int i = 1; i <= n; ++i)
        if(ex[i]) {
            Add(S,i,0), Add(i,T,0);
            Ins(T,i,0), Ins(i,S,0);
        }

    Topa(), Topb();

    for(int i = 1; i <= T; ++i) 
        ST::Insert(1,1,lim,g[i]);

    int ans = Inf,k,q;
    for(int i = 1; i <= sort[0]; ++i) {
        k = sort[i];
        for(int j = _hd[k]; j; j = _nxt[j]) 
            ST::Delete(1,1,lim,(_c[j]+g[k]+f[_to[j]]));
        ST::Delete(1,1,lim,g[k]);

        q = ST::Query(1,1,lim);

        if(q < ans) u = k, ans = q;
        else if(q == ans) u = Min(u,k);

        ST::Insert(1,1,lim,f[k]);
        for(int j = hd[k]; j; j = nxt[j])
            ST::Insert(1,1,lim,(c[j]+f[k]+g[to[j]]));
    }

    printf("%d %d\n",u,ans);

    return 0;   
}   

你可能感兴趣的:(线段树,拓扑排序)