BZOJ3832: [Poi2014]Rally 拓扑排序

给定一个N个点M条边的有向无环图,每条边长度都是1。
请找到一个点,使得删掉这个点后剩余的图中的最长路径最短。
2<=N<=500000,1<=M<=1000000
DAG可能有多个起点和终点,所以不妨建S点和T点作为所有点的起点和终点,这样整个图中的最长路就是S到T的最长路。接下来考虑如何动态维护即可。
对于每一个点很容易求出S到它的最长路和它到T的最长路,也就处理出了每一条边两端到S和T的最长路。由此可以发现,一条最长路径上的每一条边,都可以代表这条最长路。
因此我们要动态维护一个边的集合来代表整个图中的最长路状态。考虑删点时,我们只能找到与它相邻的边,因此我们维护的应该是在拓扑排序意义下一个“层”的边集,也就是一个割集,割集决定了它可以表示所有的最长路。在一个”层“中,边与边之间是近似于“平行”的关系,而每一条边就是一个最长路在当前拓扑顺序下的体现。这样,边与边之间是相对独立的,不会出现一条最长路的多个代表元素同时存在的情况。
从具体过程上来说,按照拓扑序枚举每一个点,将这一个点连接的“上一层”的边删除,再将“下一层”的边加入,两个过程中间的状态就恰好是”这个点不在“的状态。
此外,由于删点过程中可能会破坏连通性,所以边集中应维护每个点到一端的最长路。一开始都是到T的,代表”割层在这个点之前“,当到了这个点,就要将到T的删去,加入到S的,表示”割层已经走到了这个点之后“。这样才能保证删边时不对它们产生影响。
注意要按照拓扑序而不是编号来遍历节点,不要打错了。

#include<cstdio>
#include<queue>
#include<ext/pb_ds/priority_queue.hpp>
#define gm 500005
using namespace __gnu_pbds;
struct heap
{
    priority_queue<int> h;
    int del[gm];
    inline void push(int x)
    {
        if(del[x]) --del[x];
        else h.push(x);
    }
    inline void erase(int x)
    {
        ++del[x];
    }
    inline int top()
    {
        int t=h.top();
        while(del[t])
        {
            --del[t];
            h.pop();
            t=h.top();
        }
        return t;
    }
}h;
struct e
{
    int t;
    e *n;
    e(int t,e *n):t(t),n(n){}
}*f[gm],*g[gm];
int n,m,a,b;
int len=0x7fffffff,ans;
int rd[gm],s[gm],t[gm];
int sa[gm],tot=0;
inline void max(int &a,int b)
{
    if(a<b) a=b;
}
inline void sort()
{
    static std::queue<int> q;
    for(int i=1;i<=n;++i)
    if(!rd[i]) s[i]=1,q.push(i);
    while(!q.empty())
    {
        int now=sa[++tot]=q.front();q.pop();
        for(e *i=f[now];i;i=i->n)
        {
            max(s[i->t],s[now]+1);
            --rd[i->t];
            if(!rd[i->t]) q.push(i->t);
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    while(m--)
    {
        scanf("%d%d",&a,&b);
        f[a]=new e(b,f[a]);
        g[b]=new e(a,g[b]);
        ++rd[b];
    }
    sort();
    for(int i=n;i>=1;--i)
    {
        int x=sa[i];
        if(!t[x]) t[x]=1;
        for(e *j=f[x];j;j=j->n)
        max(t[x],t[j->t]+1);
        h.push(t[x]);
    }
    for(int i=1;i<=n;i++)
    {
        int x=sa[i];
        for(e *j=g[x];j;j=j->n)
        h.erase(s[j->t]+t[x]);
        h.erase(t[x]);
        int tp=h.top();
        if(tp<len) len=tp,ans=x;
        for(e *j=f[x];j;j=j->n)
        h.push(s[x]+t[j->t]);
        h.push(s[x]);
    }
    printf("%d %d\n",ans,len-1);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值