【BZOJ3832】Rally(POI2014)-拓扑排序+最长路+堆

测试地址:Rally
题目大意:一个n(5×105)个点,m(106)条边的DAG,要求删掉一个点使得图的最长路最短,要求找到这个点以及最短的最长路长。
做法:本题需要用到拓扑排序+最长路+堆。
我们通常使用的求DAG的最长路的方法是,对整个图进行拓扑序DP。然而放在这题里这样做就不行了,我们必须找到另一种求最长路的方法。
我们可以仿照网络流,建一个源点和一个汇点,从源点向每个点连有向边,从每个点向汇点连有向边,那么DAG的最长路就转化成源点到汇点的最长路,只不过要减去2,因为这不影响大小关系,所以我们直接继续讨论。
对于一条边(u,v),令val(x,0/1)为源点到x或者x到汇点的最长路,不难想到,过边(u,v)的最长路长度为val(u,0)+val(v,1)+1。但是整张图的最长路不一定过这条边,于是我们可以维护一个边集,使得图的最长路一定经过其中的边,不难发现一个割集就满足这样的条件。把上面那个式子看做边的权值,那么求整张图的最长路就是求对应割集的边权最大值。
找到了另一种求最长路的方法,我们就可以考虑删掉一个点的情况了。因为删掉一个点后,指向这个点或者从这个点指出的边都会被同时删除。那么我们需要维护一个割集,并且这个割集必须满足一个条件:删掉该点后,割集中涉及到的所有点权没有发生变化。
为了方便,我们设这个割集把点分成S,T两个集合,分别表示源点和汇点所在的集合。枚举要删除的点,要保证割集中的边两端的点权不发生变化,即要保证:删除的点的拓扑序不在S集中有边指向T集的点之前,删除的点的拓扑序不在T集中有边从S集指向的点之后。这个条件看似复杂,实际上要满足这个条件很简单:只需要保证删掉的点是S集中拓扑序最大的点就行了。因此我们一开始令所有点都在T集中(除了源点),然后按拓扑序枚举要删除的点,那么删除这一点就要把割集中与它相关的边全部删掉,求出割集中边权的最大值后,把这个点归入S集中,并把从它出发的所有边加入到割集中。
注意到删除,插入和维护最大值可以用堆来维护,看上去定点删除需要手写堆,但不难发现一条边仅会出现在割集中一次,因此只需要用一个标记数组标记它有没有被删掉过即可。那么我们就完成了此题,时间复杂度为O(mlogm)
(这题好神……转化求最长路方法妙是妙,但也有迹可循,学习了)
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
int n,m,S,T,first[500010]={0},qfirst[500010]={0},tot=0;
int in[500010]={0},val[500010][2]={0},q[500010];
struct edge
{
    int v,next;
}e[2000010],qe[2000010];
struct Pair
{
    int id,val;
    bool operator < (Pair a) const
    {
        return val<a.val;
    }
};
bool inst[2000010]={0};
priority_queue<Pair> Q;

void insert(int a,int b)
{
    e[++tot].v=b,e[tot].next=first[a],first[a]=tot;
    qe[tot].v=a,qe[tot].next=qfirst[b],qfirst[b]=tot;
}

void init()
{
    scanf("%d%d",&n,&m);
    S=n+1,T=n+2;
    for(int i=1;i<=m;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        insert(a,b);
    }
    for(int i=1;i<=n;i++)
        insert(S,i),insert(i,T);
}

void pre()
{
    int h,t;

    for(int i=1;i<=m;i++)
        in[e[i].v]++;
    h=1,t=0;
    for(int i=1;i<=n;i++)
        if (!in[i]) q[++t]=i,val[i][0]=0;
    while(h<=t)
    {
        int v=q[h++];
        for(int i=first[v];i;i=e[i].next)
        {
            in[e[i].v]--;
            val[e[i].v][0]=max(val[e[i].v][0],val[v][0]+1);
            if (!in[e[i].v]) q[++t]=e[i].v;
        }
    }

    for(int i=1;i<=m;i++)
        in[qe[i].v]++;
    h=1,t=0;
    for(int i=1;i<=n;i++)
        if (!in[i]) q[++t]=i,val[i][1]=0;
    while(h<=t)
    {
        int v=q[h++];
        for(int i=qfirst[v];i;i=qe[i].next)
        {
            in[qe[i].v]--;
            val[qe[i].v][1]=max(val[qe[i].v][1],val[v][1]+1);
            if (!in[qe[i].v]) q[++t]=qe[i].v;
        }
    }

    val[T][1]=-1;
}

void work()
{
    int h,t;

    for(int i=1;i<=m;i++)
        in[e[i].v]++;
    h=1,t=0;
    for(int i=1;i<=n;i++)
        if (!in[i]) q[++t]=i,val[i][0]=0;

    Pair nxt;
    for(int i=first[S];i;i=e[i].next)
    {
        nxt.id=i;
        nxt.val=val[e[i].v][1];
        inst[i]=1;
        Q.push(nxt);
    }

    int ans=1000000000,ansi;
    while(h<=t)
    {
        int v=q[h++];
        for(int i=qfirst[v];i;i=qe[i].next)
            inst[i]=0;

        Pair x=Q.top();
        while(!inst[x.id]) Q.pop(),x=Q.top();
        if (ans>x.val)
        {
            ansi=v;
            ans=x.val;
        }

        for(int i=first[v];i;i=e[i].next)
        {
            nxt.id=i;
            nxt.val=val[v][0]+val[e[i].v][1]+1;
            inst[i]=1;
            Q.push(nxt);
            in[e[i].v]--;
            if (!in[e[i].v]) q[++t]=e[i].v;
        }
    }

    printf("%d %d",ansi,ans);
}

int main()
{
    init();
    pre();
    work();

    return 0;
}
posted @ 2018-05-14 22:42  Maxwei_wzj  阅读(161)  评论(0编辑  收藏  举报