1. 程式人生 > >【[USACO15JAN]草鑑定Grass Cownoisseur】

【[USACO15JAN]草鑑定Grass Cownoisseur】

這大概是我寫過的除了樹剖以外最長的程式碼了吧

首先看到有向圖和重複經過等敏感詞應該能想到先tarjan後縮點了吧

首先有一個naive的想法,既然我們要求只能走一次返回原點,那我們就正著反著建兩遍圖,分別處理出1到其他點的所能經過的最多點數和其他點到1經過的最大點數,之後找到那些和1有正邊或反邊相連的點,之後逆行這一條邊,取一個max就好了

於是洋洋灑灑寫了100多行發現連樣例都過不了

本著交一交試試看的心態交了上去,發現還能過四個點

於是就去手畫了一遍樣例,發現自己真的是非常naive

樣例是這個樣子的

樣例

儘管很醜,但就勉強看看吧

我們發現按照剛才那個思路我們顯然是要掛的

因為按照那樣跑出來的結果是4,但這裡的最佳方案應該是從1到那個縮好的點再到5,之後逆行一次走到3,之後走回1

這樣我們如果只考慮能到到1的或只考慮能被1到達的,顯然是不行的

這樣不行怎麼辦,我們放到一起考慮就好了

如果一個能被1到達的點(比如說樣例裡的5)有一條邊(當然這是一條反邊)能到達一個能到達1的點,我們就可以把這兩種情況一起考慮

同理一個能到達1的點有一條邊(自然這是一條正邊)和一個能被1到達的點相連,這兩種情況也可以一起考慮

所以就是程式碼了

#include<iostream>
#include<cstring>
#include<cstdio>
#include<bitset>
#define re register
#define maxn 100001
using namespace std;
struct node
{
    int v,nxt;
}e[maxn],e1[maxn],e2[maxn];
int head[maxn],head1[maxn],head2[maxn];
int dfn[maxn],low[maxn],st[maxn],belong[maxn],dp1[maxn],d[maxn],dp2[maxn];
int f1[maxn],f2[maxn];
int q[maxn],r[maxn],c[maxn];
bitset<maxn> f;//閒的沒事幹開了bitset
int n,m,top,k,p,num,num1,num2,mid;
inline void add_edge(int x,int y)
{
    e[++num].v=y;
    e[num].nxt=head[x];
    head[x]=num;
}//原圖
inline void add_edge_1(int x,int y)
{
    e1[++num1].v=y;
    e1[num1].nxt=head1[x];
    head1[x]=num1;
}//縮點後新圖建正邊
inline void add_edge_2(int x,int y)
{
    e2[++num2].v=y;
    e2[num2].nxt=head2[x];
    head2[x]=num2;
}//縮點後新圖建反邊
inline int read()
{
    char c=getchar();
    int x=0;
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9')
      x=(x<<3)+(x<<1)+c-48,c=getchar();
    return x;
}
void tarjan(int x)
{
    dfn[x]=low[x]=++k;
    f[x]=1;
    st[++top]=x;
    for(re int i=head[x];i;i=e[i].nxt)
    if(!dfn[e[i].v]) tarjan(e[i].v),low[x]=min(low[x],low[e[i].v]);
    else if(f[e[i].v]) low[x]=min(low[x],dfn[e[i].v]);
    if(dfn[x]==low[x])
    {
        p++;
        do
        {
            mid=st[top--];
            f[mid]=0;
            d[p]++;//記錄新點點權
            belong[mid]=p;//記錄好每一個點屬於哪一個縮完點後的新點
        }while(x!=mid);
    }
}
int main()
{
    n=read();
    m=read();
    int x,y;
    for(re int i=1;i<=m;i++)
    {
        x=read();
        y=read();
        add_edge(x,y);
    }
    for(re int i=1;i<=n;i++)
    if(!dfn[i]) tarjan(i);//縮點!
    for(re int i=1;i<=n;i++)
    for(re int j=head[i];j;j=e[j].nxt)
    if(belong[i]!=belong[e[j].v]) 
    {
        r[belong[e[j].v]]++,add_edge_1(belong[i],belong[e[j].v]);//建正圖
        c[belong[i]]++,add_edge_2(belong[e[j].v],belong[i]);//建反圖
    }
    f[belong[1]]=1;//我們開一個標記陣列,標記那些點可以被1到達,拓撲排序的時候只有這些點才進行動規,其餘的點只做刪邊操作
    dp1[belong[1]]=d[belong[1]];
    int tot=0;
    for(re int i=1;i<=p;i++)
    if(!r[i]) q[++tot]=i;
    for(re int i=1;i<=tot;i++)
    {
        for(re int j=head1[q[i]];j;j=e1[j].nxt)
        {
            r[e1[j].v]--;
            if(f[q[i]])
            {
                f[e1[j].v]=1;
                dp1[e1[j].v]=max(dp1[e1[j].v],dp1[q[i]]+d[e1[j].v]);
                //dp1[i]表示從1到i形成的最大點權
            }
            if(!r[e1[j].v]) q[++tot]=e1[j].v;
        }
    }
    int ans=0;
    for(re int i=head1[belong[1]];i;i=e1[i].nxt)
        ans=max(ans,dp1[e1[i].v]);
    for(re int i=1;i<=p;i++)
        f1[i]=f[i];//記錄那些點可以被1到達
    tot=0;
    memset(q,0,sizeof(q));
    f.reset();//bitset清零
    f[belong[1]]=1;//標記同理,表示這個點可以到達1
    dp2[belong[1]]=d[belong[1]];
    for(re int i=1;i<=p;i++)
    if(!c[i]) q[++tot]=i;
    for(re int i=1;i<=tot;i++)
    {
        for(re int j=head2[q[i]];j;j=e2[j].nxt)
        {
            c[e2[j].v]--;
            if(f[q[i]])
            {
                f[e2[j].v]=1;
                dp2[e2[j].v]=max(dp2[e2[j].v],dp2[q[i]]+d[e2[j].v]);
                //dp2[i]表示i這個點到1形成的最大點權
            }
            if(!c[e2[j].v]) q[++tot]=e2[j].v;
        }
    }
    for(re int i=head2[belong[1]];i;i=e2[i].nxt)
        ans=max(ans,dp2[e2[i].v]);
    for(re int i=1;i<=p;i++)
        f2[i]=f[i];//記錄那些點可以到達1
    for(re int i=1;i<=p;i++)
    if(f1[i])//這個點可以被1到達
    {
        for(re int j=head2[i];j;j=e2[j].nxt)//逆行一次
        if(f2[e2[j].v]) ans=max(ans,dp1[i]+dp2[e2[j].v]-d[belong[1]]);//由於兩次dp都把1所在的強聯通分量的點權算了進去,所以要減去一個1所在的強聯通分量的點權
    }
    for(re int i=1;i<=p;i++)
    if(f2[i])
    {
        for(re int j=head1[i];j;j=e1[j].nxt)
        if(f1[e1[j].v]) ans=max(ans,dp2[i]+dp1[e1[j].v]-d[belong[1]]);
    }//同理
    ans=max(d[belong[1]],ans);//如果一誰都到達不了,誰也到不了,那麼答案就是1的點權了
    printf("%d",ans);
    return 0;
}