【[USACO15JAN]草鑑定Grass Cownoisseur】
阿新 • • 發佈:2019-01-02
這大概是我寫過的除了樹剖以外最長的程式碼了吧
首先看到有向圖和重複經過等敏感詞應該能想到先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; }