BZOJ 3832 POI2014 Rally

19 篇文章 0 订阅
9 篇文章 0 订阅

Problem

BZOJ又是令人窒息的权限题
洛谷

Solution

感觉最近好像做了一些思路很神的神题。。
之前做一道题,看了好久才发现是线段树……
本以为这个在图上总不是线段树了吧,结果一看题解又是线段树
我开始怀疑我到底有没有学过线段树了

先引入超级源汇点,那么最长的路径就是源汇点之间的路径了,我们将图进行正反向分别dp出到源/汇的最长路径。不妨用f[x]表示从源点到x的最长路,g[x]表示从x到汇点的最长路。容易知道对于一条单向边(u,v),其贡献为f[u]+g[v]+1。
我们考虑删除一个点之后,也就意味着所有与其有关的边均无效。由于每条边的边权均为1,那么路径最长为n,考虑用权值线段树维护所有的路径。

唔,可能讲的不如clove_hxy清楚,不如贴一下大佬的原话好惹。就两点需要注意的:cnt减到负数的时候还是要重置回0,因为要保证加入新边会产生影响,而不是补上负数。然后是按照拓扑序来进行删点,因为如果先枚举了拓扑序在后的节点,就会对需要经过其到终点且还未被枚举过的点产生影响。

在删除节点的时候要把所有用他的入边更新的答案删去,再把g[i]从这个点开始的最长路删去,为什么只用删去这一条路呢?因为我们刚开始加入的时候就只加入了这个点之后的最长路。然后这时线段树中的最大值就是删去该点后的答案。那么有人会有疑问?一条最长路上的每条边能产生的最长路的值都是相同的那么只删除这个点的,会不会在这条路径上的其他边的最大值会影响答案呢?其实是不会的,因为我们是按照拓扑序来搞的,他后面的点只加入了g[x],根本没有加入这条路径的信息。

然后再把这个点的出边所能产生的最长路加入线段树,并把f[i]加入线段树,因为如果之后再用该点更新答案的话,一定会选择到该点的最长路,其他的路径就没有用了。之所以要把所有出边产生的最长路加入线段树,是因为在删除这个点之后的点的时候,可能会把这个点以后最长路破坏,所有需要把所有路径记录下来,保证可以通过所有可能的路径更新答案。

Code

#include <cstdio>
#include <queue>
#define rg register
using namespace std;
const int maxn=500010,maxm=1000010,INF=0x3f3f3f3f;
struct data{int v,nxt;}e[maxm],edge[maxm];
int n,m,p,ep,ans=INF,ps,in[maxn],out[maxn],h[maxn],head[maxn];
int top[maxn],f[maxn],g[maxn],mx[maxn<<2],cnt[maxn<<2];
queue<int> q;
template <typename Tp> inline void read(Tp &x)
{
    x=0;char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
inline int max(int x,int y){return x>y?x:y;}
inline void insert(int u,int v){e[++p]=(data){v,h[u]};h[u]=p;}
inline void insert1(int u,int v){edge[++ep]=(data){v,head[u]};head[u]=ep;}
void input()
{
    read(n);read(m);
    for(rg int i=1,x,y;i<=m;i++)
    {
        read(x);read(y);
        insert(x,y);in[y]++;
        insert1(y,x);out[x]++;
    }
}
void update(int l,int r,int pos,int val,int rt)
{
    if(l==r)
    {
        cnt[rt]+=val;
        if(cnt[rt]>0) mx[rt]=l;
        else mx[rt]=-1,cnt[rt]=0;
        return ;
    }
    int m=(l+r)>>1;
    if(pos<=m) update(l,m,pos,val,rt<<1);
    else update(m+1,r,pos,val,rt<<1|1);
    mx[rt]=max(mx[rt<<1],mx[rt<<1|1]);
}
void init()
{
    int x,cnt=0;
    while(!q.empty()) q.pop();
    for(rg int i=1;i<=n;i++) if(!in[i]) q.push(i),top[++cnt]=i;
    while(!q.empty())
    {
        x=q.front();q.pop();
        for(int i=h[x];i;i=e[i].nxt)
        {
            if(f[e[i].v]<f[x]+1) f[e[i].v]=f[x]+1;
            in[e[i].v]--;
            if(!in[e[i].v]) q.push(e[i].v),top[++cnt]=e[i].v;
        }
    }
    while(!q.empty()) q.pop();
    for(rg int i=1;i<=n;i++) if(!out[i]) q.push(i);
    while(!q.empty())
    {
        x=q.front();q.pop();
        for(int i=head[x];i;i=edge[i].nxt)
        {
            if(g[edge[i].v]<g[x]+1) g[edge[i].v]=g[x]+1;
            out[edge[i].v]--;
            if(!out[edge[i].v]) q.push(edge[i].v);
        }
    }
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    #endif
    input();
    init();
    for(rg int i=1;i<=n;i++) update(0,n,g[i],1,1);
    for(rg int i=1;i<=n;i++)
    {
        int x=top[i];
        for(int j=head[x];j;j=edge[j].nxt) update(0,n,g[x]+f[edge[j].v]+1,-1,1);
        update(0,n,g[x],-1,1);
        if(mx[1]<ans) ans=mx[1],ps=x;
        for(int j=h[x];j;j=e[j].nxt) update(0,n,f[x]+g[e[j].v]+1,1,1);
        update(0,n,f[x],1,1);
    }
    printf("%d %d\n",ps,ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值