1. 程式人生 > >P2272 [ZJOI2007]最大半連通子圖

P2272 [ZJOI2007]最大半連通子圖

鏈式前向星 color main 累加 == 有向無環圖 min 無環 spa

傳送門

題目簡單來說就是給一個有向圖,將圖轉化為DAG圖後,求圖中最長鏈及最長鏈的個數。

思路

  用 tarjan 縮點重構將原圖轉換為一個有向無環圖,讓後在新圖上跑 topo 求出最長鏈。

  最長鏈的個數可以用動態規劃,設 e[ i ] 表示新圖中以 i 為終點的方案數,那麽 e[ i ] 就等於連到 i 且滿足距離等於起點到 i 的臨時最長距離的點的 e 之和。

  最後查找距離等於最長鏈的點,答案就是它們的方案數之和。

標程

#include<iostream>
#include<cstdio>
#include<algorithm>
#include
<cmath> #include<cstring> #include<string> #include<cstdlib> #include<stack> #include<vector> #include<queue> #include<deque> #include<map> #include<set> using namespace std; #define maxn 1000001 int n,m,mod; int x[maxn],y[maxn];//存邊
int cnt=0; int de[maxn],to[maxn],head[maxn],nex[maxn];//鏈式前向星 int ue[maxn]; int t=0,w=0; int ans=0; int e[maxn]; int dis[maxn];//topo int num=0,top=0,col=0; int dfn[maxn],low[maxn],st[maxn],co[maxn],ins[maxn];//tarjan int nu[maxn];//重新記錄邊(去重) inline int read() { int kr=1,xs=0; char ls; ls
=getchar(); while(!isdigit(ls)) { if(!(ls^45)) kr=-1; ls=getchar(); } while(isdigit(ls)) { xs=(xs<<1)+(xs<<3)+(ls^48); ls=getchar(); } return xs*kr; } inline void add(int x,int y) { nex[++cnt]=head[x]; head[x]=cnt; to[cnt]=y; } inline void tarjan(int u)//tarjan縮點 { dfn[u]=low[u]=++num; st[++top]=u; for(int i=head[u];i;i=nex[i]) { int v=to[i]; if(!dfn[v]) { tarjan(v); low[u]=min(low[u],low[v]); } else if(!co[v]) low[u]=min(low[u],dfn[v]); } if(low[u]==dfn[u]) { co[u]=++col; ++ins[col];//ins為強連通分量的大小 while(st[top]!=u) { ++ins[col]; co[st[top]]=col; --top; } --top; } } inline bool cmp(int a,int b) { if(x[a]!=x[b]) return x[a]<x[b]; return y[a]<y[b]; } inline void remove()//去除重邊,否則會影響方案數的統計 { for(int i=1;i<=m;i++) { nu[i]=i; x[i]=co[x[i]]; y[i]=co[y[i]]; } sort(nu+1,nu+m+1,cmp); } inline void build_map() { remove(); //去除重邊 cnt=0; memset(head,0,sizeof(head)); for(int i=1;i<=m;i++) { int z=nu[i]; if((x[z]!=y[z])&&(x[z]!=x[nu[i-1]]||y[z]!=y[nu[i-1]])) { ++de[y[z]]; add(x[z],y[z]); } } }//重構圖 inline void topo() { for(int i=1;i<=col;i++) { if(!de[i]) { ue[++w]=i; dis[i]=ins[i]; e[i]=1; if(dis[ans]<dis[i]) ans=i; } }//topo排序初始入隊 while(t<w) { int u=ue[++t]; for(int i=head[u];i;i=nex[i]) { int v=to[i]; --de[v]; if(dis[v]<dis[u]+ins[v])//臨時最長距離被更新,重新統計方案數 { dis[v]=dis[u]+ins[v]; e[v]=0; if(dis[ans]<dis[v]) ans=v; } if(dis[v]==dis[u]+ins[v])//滿足距離條件,累加方案數 { e[v]=(e[v]+e[u])%mod; } if(!de[v]) ue[++w]=v; } }//topo排序 } int main() { n=read();m=read();mod=read(); for(int i=1;i<=m;i++) { x[i]=read();y[i]=read(); add(x[i],y[i]); } for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); build_map();//重構圖 topo();//topo尋找最長鏈和最長鏈個數 int tot=0; for(int i=1;i<=n;i++) if(dis[i]==dis[ans])//統計答案 tot=(tot+e[i])%mod; printf("%d\n%d\n",dis[ans],tot);//輸出最長距離、方案數 return 0; }

P2272 [ZJOI2007]最大半連通子圖