題解 P2272 【[ZJOI2007]最大半連通子圖】

P2272 [ZJOI2007]最大半連通子圖

萌新初學Tarjan,在《信息學奧賽一本通-提升篇》中看到這題,看到題解很少,便想發佈一篇較爲清新簡潔的題解。——第5道紫題算法

題目大意:性能

定義最大半連通圖:對於圖中任意兩點u,v,存在一條u到v的有向路徑 或者 從v到u的有向路徑。求一個圖中不一樣的最大半連通子圖的數目。spa

看到題面時你們很容易想到,若是兩點互相能夠到達,那麼它們必是半連通圖,因此考慮先Tarjan縮點(P3387 【模板】縮點(Tarjan縮點+DAGdp)code

接着去除重邊從新建圖,你會發現,在這個有向無環圖(DAG)中,半連通子圖都是一條鏈(能夠舉反例試試,這條鏈不可能有分支,不然將有兩點沒法抵達另外一方)排序

因而,G的最大半連通子圖擁有的節點數K就是最長鏈長度,不一樣的最大半連通子圖的數目就是最長鏈個數。get

信息學一本通:最長鏈能夠直接用拓撲排序(topo),最長鏈個數用一個相似DP的方法,用f【i】表示以 i 爲終點的方案數,那麼f【i】就等於知足距離爲起點到 i 的臨時最短距離的點的 f 的和。而後查找距離等於最長鏈的點,答案爲它們的方案數之和string

其餘題解中已經給出了拓撲的算法,我借鑑大佬的程序用的是搜索,先一直搜到終點再回來更新答案。因爲數據範圍#7一直RE,後來改成const int N=1e5+5,M=2e6+5;終於AC。。qwq高性能。。it

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=1e5+5,M=2e6+5;

bool f[N];
//f 在搜索中判斷是否走過

int n,m,mod,now,d[N],a[N],ans,maxans,ch[N];
//d指從u到終點的最長鏈距離,a指最長鏈點數,ch指出度

int h[N],u[M],v[M],r[N],nu[M],cnt;
//h是鏈式前向星的建邊head,u,v保存初始讀入的邊左右兩點,nu存初始時邊的編號,r是入度

int top,co,dfn[N],low[N],c[N],s[N],st[N];
//dfn,low,st用於Tarjan,c表所在強連通份量編號,s指所在強連通份量點數

struct edge {
   int h,to;
} e[M];

#define rint register int
#define min(a,b) (a<b? a:b)
#define max(a,b) (a>b? a:b)

inline bool cmp(int a,int b) {
   return u[a]<u[b] || (u[a]==u[b] && v[a]<v[b]);
}//將邊排序,方便從新建圖

inline void add(int u,int v) {
   e[++cnt].h=h[u],h[u]=cnt,e[cnt].to=v;
}

inline int read() {
   int w=1,ans=0;
   char ch=getchar();
   while(ch>'9'||ch<'0') if (ch=='-') w=-1,ch=getchar();
   while(ch<='9'&& ch>='0') ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
   return ans*w;
}

inline void Tarjan(int u) {
   dfn[u]=low[u]=++now;
   st[++top]=u;
   for (rint i=h[u]; i; i=e[i].h) {
       int v=e[i].to;
       if (!dfn[v])
           Tarjan(v),low[u]=min(low[u],low[v]);
       else if (!c[v])
           low[u]=min(low[u],dfn[v]);
   }
   if (low[u]==dfn[u]) {
       c[u]=++co,s[co]++;
       while(st[top]!=u)
           s[co]++,c[st[top]]=co,top--;
       top--;
   }
}//標準縮點

inline void dfs(int u) {
   f[u]=1;
   if (!ch[u]) {//若是沒有出度,即到頭了
       d[u]=s[u],a[u]=1;//距離爲點數,以u爲起點方案爲1
       maxans=max(maxans,d[u]);//更新最長鏈距離
       return;
   }
   for (rint i=h[u]; i; i=e[i].h) {
       int v=e[i].to;
       if (!f[v]) dfs(v);//繼續搜索鏈的後面
       if (d[v]+s[u]>d[u])//若以u爲起點的鏈距離能夠更長
           d[u]=d[v]+s[u],a[u]=a[v]%mod;//更新
       else if (d[u]==d[v]+s[u])//若最長鏈距離相同
           a[u]=(a[u]+a[v])%mod;//加上方案數
       maxans=max(maxans,d[u]);
   }
}

int main() {
   n=read(),m=read(),mod=read();
   for (rint i=1; i<=m; i++) u[i]=read(),v[i]=read(),add(u[i],v[i]);
   for (rint i=1; i<=n; i++) if (!dfn[i]) Tarjan(i);

   cnt=0;
   memset(h,0,sizeof h);
   memset(e,0,sizeof e);
   for (rint i=1; i<=m; i++)
       nu[i]=i,u[i]=c[u[i]],v[i]=c[v[i]];

   sort(nu+1,nu+m+1,cmp);//按u,v排序邊
   for (rint i=1; i<=m; i++)
   {
       int num=nu[i];
       if (u[num]!=v[num] && (u[num]!=u[nu[i-1]] || v[num]!=v[nu[i-1]]))//若此邊不是自環,且與上一條邊不一樣(去除重邊)
          ++ch[u[num]],++r[v[num]],add(u[num],v[num]);}
//出度入度加1,加邊
   for (rint i=1; i<=co; i++) if (!r[i] && !f[i]) dfs(i);//入度爲0且未搜索過
   for (rint i=1; i<=co; i++) if (d[i]==maxans) ans=(ans+a[i])%mod;//統計答案
   printf("%d\n%d\n",maxans,ans);
}
相關文章
相關標籤/搜索