【BZOJ3832】[POI2004]Rally(拓扑排序,动态规划)

【BZOJ3832】[POI2004]Rally(拓扑排序,动态规划)

题面

BZOJ,权限题
洛谷

题解

这题好强啊,感觉学了好多东西似的。
首先发现了一个图画的很好的博客,戳这里
然后我来补充一下这题到底怎么做。
首先这个图是一个\(DAG\),我们对其进行拓扑排序,设\(f[i]\)表示以\(i\)开头的最长链长度,\(g[i]\)表示以\(i\)结尾的最长链长度,那么经过某条边\(u\rightarrow v\)的边贡献的最长路的贡献就是\(g[u]+f[v]+1\)
我们发现,如果我们删除某一个点\(x\),那么我们必定能够把整个图分成两个部分,左侧我们认为是拓扑序小于\(x\)的点集,右侧是拓扑序大于\(x\)的点集,那么最终的答案显然是从左侧的某一个点连向右侧的某一个点能够贡献的最长链。所以我们只需要动态的维护左侧连向右侧的边的贡献就好了。我们先假设所有点都在右侧,按照拓扑序依次把所有点移到左侧就好了。我们可以直接在这个过程中统计答案。首先我们把所有左侧连向当前点的边的贡献全部删掉,然后统计一下答案,然后再把这个点连向右侧的边的贡献全部加入进来。当然,不仅仅只有边的贡献,显然\(f,g\)两个数组可以产生贡献,左侧的点产生\(g\)的贡献,右侧产生\(f\)的贡献,也和边的贡献一起丢进什么数据结构维护一下就好了。因为每条边只会加一次,删一次,所以复杂度是\(O((n+m)log)\)的。

#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
#define MAX 500500
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
struct Heap
{
    priority_queue<int> Q1,Q2;
    void push(int x){Q1.push(x);}
    void del(int x){Q2.push(x);}
    bool empty(){while(!Q2.empty()&&Q1.top()==Q2.top())Q1.pop(),Q2.pop();return Q1.empty();}
    int top(){if(empty())return 1e9;return Q1.top();}
}S;
int n,m,ans1=1e9,ans2;
struct Line{int v,next;}e[MAX<<2];
int h[MAX],cnt=1,dg[MAX];
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
int Q[MAX],f[MAX],g[MAX];
void Topsort()
{
    int he=1,t=0;
    for(int i=1;i<=n;++i)if(!dg[i])Q[++t]=i;
    while(he<=t)
    {
        int u=Q[he++];
        for(int i=h[u];i;i=e[i].next)
            if(i&1)if(!--dg[e[i].v])Q[++t]=e[i].v;
    }
    for(int u=1;u<=n;++u)
        for(int i=h[Q[u]];i;i=e[i].next)
            if(i&1)g[e[i].v]=max(g[e[i].v],g[Q[u]]+1);
    for(int u=n;u>=1;--u)
        for(int i=h[Q[u]];i;i=e[i].next)
            if(i&1)f[Q[u]]=max(f[Q[u]],f[e[i].v]+1);
}
int main()
{
    n=read();m=read();
    for(int i=1,u,v;i<=m;++i)u=read(),v=read(),Add(u,v),Add(v,u),++dg[v];
    Topsort();
    for(int i=1;i<=n;++i)S.push(f[i]);
    for(int j=1;j<=n;++j)
    {
        int u=Q[j];S.del(f[u]);
        for(int i=h[u];i;i=e[i].next)
            if(!(i&1))S.del(f[u]+g[e[i].v]+1);
        int d=S.top();S.push(g[u]);
        if(d<ans1)ans1=d,ans2=u;
        for(int i=h[u];i;i=e[i].next)
            if(i&1)S.push(g[u]+f[e[i].v]+1);
    }
    printf("%d %d\n",ans2,ans1);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/cjyyb/p/9788582.html
今日推荐