【图论-拓扑序】BZOJ3832 [Poi2014]Rally

【题目】
原题地址
给定一个N个点M条边的有向无环图,每条边长度都是1。
请找到一个点,使得删掉这个点后剩余的图中的最长路径最短。

【题目分析】
首先这是一个DAG,我们还是很容易联想到DAG的一些特性,比如Topsort之类的,在这题就很有用了qwq。

【解题思路】
首先当然是建超级源超级汇。
因为这是一个DAG,所以我们就跑一下dp,算出源点到所有点的最长路(记作 f f )以及所有点到汇的最长路(记作g)。
令边 x>y x − > y 的权值为 f[x]+g[y] f [ x ] + g [ y ] ,那么图的最长链转化为边权最大值。
接下来我们要做的就是枚举删哪个点,然后用数据结构维护一下删点后割集中最大边权。

考虑DAG的特性,我们还是可以用一下拓扑排序,发现按拓扑序进行删点就行了。
令初始 S S 集只有S,所有点和 T T 都在T
按照拓扑序依次将每个点从 T T 集中删掉,加入S
首先将这个点的所有入边从数据结构中删掉 此时割集的边权最大值就是删掉这个点的答案。
然后再将这个点的所有出边加入数据结构中即可。
这个插入、删除、查询,用堆就行了(不要问我怎么删除,开一个新的堆就行了),当然看到有人写线段树也是可以的qwq。

【参考代码】

#include<bits/stdc++.h>
using namespace std;

const int INF=1e9;
const int N=5e5+10;
const int M=2e6+10;
int n,m,h,t,mn,ans;
int du[M],qs[M],w[M];
priority_queue<int>p,q; 

struct Tst
{
    int u[M],v[M],nex[M],head[N],dis[N],du[N],tot;

    inline void add(int x,int y)
    {
        ++tot;
        u[tot]=x;v[tot]=y;
        nex[tot]=head[x];head[x]=tot;
    }

    inline void solve(int S)
    {
        memset(dis,0,sizeof(dis));
        h=t=1;qs[1]=S;
        while(h<=t)
        {
            int x=qs[h++];
            for(int i=head[x];i;i=nex[i])
            {
                int y=v[i];
                dis[y]=max(dis[y],dis[x]+1);
                du[y]--;
                if(!du[y])
                    qs[++t]=y;
            }
        }
    }
};
Tst A,B;

inline void dele(int x)
{
    q.push(x);
    while(!p.empty() && !q.empty() && q.top()==p.top())
    {
        q.pop();
        p.pop();
    }
}

int read()
{
    int x=0;char c='.';
    while(c<'0' || c>'9')
        c=getchar();
    while(c>='0' && c<='9')
    {
        x=x*10+(c-'0');
        c=getchar();
    }
    return x;
}

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

    n=read();m=read();
    for(int i=1;i<=m;++i)
    {
        int u,v;
        u=read();v=read();
        du[v]++;A.du[v]++;B.du[u]++;
        A.add(u,v);B.add(v,u);
    }
    for(int i=1;i<=n;++i)
    {
        du[i]++;du[n+1]++;
        A.du[i]++;A.du[n+1]++;
        B.du[i]++;B.du[0]++;
        A.add(0,i);A.add(i,n+1);
        B.add(i,0);B.add(n+1,i);
    }
    A.solve(0);B.solve(n+1);

    for(int i=1;i<=m+2*n;++i)
        w[i]=A.dis[A.u[i]]+B.dis[A.v[i]]-1;

    h=t=1;qs[1]=0;
    mn=INF;
    while(h<=t)
    {
        int x=qs[h++];
        for(int i=B.head[x];i;i=B.nex[i])
        dele(w[i]);
        if(x!=0 && x!=n+1)
        {
            int y=p.top();
            if(y<mn)
                mn=y,ans=x;
        }
        for(int i=A.head[x];i;i=A.nex[i])
        {
            int y=A.v[i];
            p.push(w[i]);
            du[y]--;
            if(!du[y])
                qs[++t]=y;
        }
    }
    printf("%d %d\n",ans,mn);

    return 0;
}

【总结】
这道题挺不错的qwq,然后我因为memset的数组空间开成了4倍,所以…TLE了两发。
DAG的特性牢记在心。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值