題目描述
一個有向圖稱爲半連通的(Semi-Connected),如果滿足:,滿足或,即對於圖中任意兩點,存在一條到的有向路徑或者從到的有向路徑。
若滿足,是中所有跟有關的邊,則稱是的一個導出子圖。
若是的導出子圖,且半連通,則稱爲的半連通子圖。若是所有半連通子圖中包含節點數最多的,則稱是的最大半連通子圖。
給定一個有向圖,請求出的最大半連通子圖擁有的節點數,以及不同的最大半連通子圖的數目。由於可能比較大,僅要求輸出對的餘數。
輸入格式
第一行包含兩個整數。分別表示圖的點數與邊數,的意義如上文所述接下來行,每行兩個正整數,表示一條有向邊。圖中的每個點將編號爲,保證輸入中同一個不會出現兩次。
輸出格式
應包含兩行,第一行包含一個整數。第二行包含整數。
數據範圍
對於20%的數據,;
對於60%的數據,;
對於100%的數據,;
對於100%的數據,。
分析
首先可以知道,對於一條鏈,肯定是半連通圖,從入度爲0的點開始,依次可以遍歷到它之後的點;對於一個強連通圖,一定是半連通圖。然後再考慮原來的問題,若給定的圖是個有向無環圖,則求最大的半連通子圖即爲求最長鏈,個數即爲求最長鏈的個數;因此可以將原圖縮點,形成有向無環圖,在這個新圖裏面進行拓撲排序,在過程中進行Dp,求出以每個點爲結束點的最長鏈及其個數,最後再枚舉新圖中的結束點,找出最長鏈及個數。
需要注意的是,在縮完點建新圖前將每條邊記錄下來,去重,防止個數記錄重複。
代碼
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <iomanip>
#include <queue>
using namespace std;
typedef long long LL;
const int N=100005,M=1000005;
struct Edge {
int to,nxt;
}e[M*2],e1[M*2];
struct ArrayOfEdge {
int x,y;
}ee[M];
int h[N],cnt,ans;
int h1[N],cnt1,c;
int n,m,p,du[N];
int dfn[N],low[N];
int instack[N],v[N];
int st[N],top,f[N];
int num,scc,bel[N];
LL g[N],ansn;
queue<int> q;
void Add(int x,int y) {
e[++cnt]=(Edge){y,h[x]};
h[x]=cnt;
}
void Addt(int x,int y) {
ee[++c]=(ArrayOfEdge){x,y};
}
void Add1(int x,int y) {
e1[++cnt1]=(Edge){y,h1[x]};
h1[x]=cnt1;
}
void Tarjan(int x) {
dfn[x]=low[x]=++num;
instack[x]=1;
st[++top]=x;
for (int i=h[x];i;i=e[i].nxt) {
int y=e[i].to;
if (!dfn[y]) {
Tarjan(y);
low[x]=min(low[x],low[y]);
} else if (instack[y])
low[x]=min(low[x],dfn[y]);
}
if (dfn[x]==low[x]) {
int t;
++scc;
do {
t=st[top--];
bel[t]=scc;
v[scc]++;//v爲該強連通分量的點數
instack[t]=0;
} while (t!=x);
}
}
bool cmp(ArrayOfEdge a,ArrayOfEdge b) {
if (a.x!=b.x) return a.x<b.x;
return a.y<b.y;
}
int main() {
//freopen("semi.in","r",stdin);
//freopen("semi.out","w",stdout);
scanf("%d%d%d",&n,&m,&p);
for (int i=1;i<=m;i++) {
int u,v;
scanf("%d%d",&u,&v);
Add(u,v);
}
for (int i=1;i<=n;i++)
if (!dfn[i]) Tarjan(i);
for (int x=1;x<=n;x++) {
for (int i=h[x];i;i=e[i].nxt) {
int y=e[i].to;
if (bel[x]==bel[y]) continue;
Addt(bel[x],bel[y]);//記錄
}
}
sort(ee+1,ee+c+1,cmp);//排序
for (int i=1;i<=c;i++) {
if (ee[i].x==ee[i-1].x&&ee[i].y==ee[i-1].y) continue;//去重
Add1(ee[i].x,ee[i].y);
du[ee[i].y]++;
}
for (int i=1;i<=scc;i++)
if (!du[i]) {
q.push(i);
f[i]=v[i];
g[i]++;
}
while (!q.empty()) {
int t=q.front();
q.pop();
ans=max(ans,f[t]);//找最長鏈
for (int i=h1[t];i;i=e1[i].nxt) {
int y=e1[i].to;
if (f[y]==f[t]+v[y]) {//相等時就累加方案數
g[y]=(g[y]+g[t])%p;
} else if (f[y]<f[t]+v[y]) {//DP在DAG中求最長鏈,f[i]=max{f[j]+v[i]}(j->i有邊)
f[y]=f[t]+v[y];
g[y]=g[t];//直接更新
}
du[y]--;
if (du[y]==0) q.push(y);
}
}
printf("%d\n",ans);
for (int i=1;i<=scc;i++) {
if (f[i]==ans) {
ansn=(ansn+g[i])%p;
}
}
printf("%lld",ansn);
return 0;
}