[ZJOI2007]最大半连通子图 (tarjan缩点+拓扑序dp)
[ZJOI2007]最大半连通子图
链接:https://ac.nowcoder.com/acm/problem/20603
来源:牛客网
题目描述
一个有向图G=(V,E)称为半连通的(Semi-Connected),如果满足:u,v∈V,满足u→v或v→u,即对于图中任意 两点u,v,存在一条u到v的有向路径或者从v到u的有向路径。
若G'=(V',E')满足V'?V,E'是E中所有跟V'有关的边, 则称G'是G的一个导出子图。
若G'是G的导出子图,且G'半连通,则称G'为G的半连通子图。
若G'是G所有半连通子图 中包含节点数最多的,则称G'是G的最大半连通子图。
给定一个有向图G,请求出G的最大半连通子图拥有的节点数K ,以及不同的最大半连通子图的数目C。由于C可能比较大,仅要求输出C对X的余数。
输入描述:
第一行包含两个整数N,M,X。N,M分别表示图G的点数与边数,X的意义如上文所述
接下来M行,每行两个正整数a, b,表示一条有向边(a, b)。
图中的每个点将编号为1,2,3…N,保证输入中同一个(a,b)不会出现两次。
N ≤ 100000, M ≤ 1000000;对于100%的数据, X ≤ 10^8
输出描述:
应包含两行,第一行包含一个整数K。第二行包含整数C Mod X.
示例1
输入
6 6 20070603 1 2 2 1 1 3 2 4 5 6 6 4
输出
3 3
思路:
tarjan缩点得到无环有向图DAG,对缩点后的图每个点的点权就是缩点的大小,答案就是求DAG中最长的链,以及最长的链有几条。拓扑排序后用f[ ]和g[ ]记录答案。
用拓扑序是为了不用考虑重复的情况,然后直接dp即可。
代码:
#include<bits/stdc++.h> #define sd(x) scanf("%d",&x) #define lsd(x) scanf("%lld",&x) #define ms(x,y) memset(x,y,sizeof x) #define fu(i,a,b) for(int i=a;i<=b;i++) #define fd(i,a,b) for(int i=a;i>=b;i--) #define all(a) a.begin(),a.end() #define range(a,x,y) a+x,a+y+x using namespace std; using namespace __gnu_cxx; typedef long long ll; typedef unsigned long long ull; typedef long double ld; typedef pair<int,int> P; const int N=2e5+99; ll mod=2147493647; const int INF=1e9+7; vector<int> to[N],go[N]; int dfn[N],low[N],cnt,st[N],tt,point,col[N],num[N],du[N]; bool vis[N]; int n,m; ll f[N],g[N]; map<int,int> used[N]; void tarjan(int u) { vis[u]=1; dfn[u]=low[u]=++cnt; st[++tt]=u; for(int v:to[u]) { if(!dfn[v]) { tarjan(v); low[u]=min(low[u],low[v]); } else if(vis[v]) low[u]=min(low[u],dfn[v]); } if(dfn[u]==low[u]) { point++; do { col[st[tt]]=point; num[point]++; vis[st[tt]]=0; } while (st[tt--]!=u); } } void topusort() { queue<int> q; fu(i,1,point) { if(du[i]==0) q.push(i); g[i]=1; f[i]=num[i]; } while(!q.empty()) { int x=q.front();q.pop(); for(int y:go[x]) { //记录答案 if(f[y]<f[x]+num[y]) { f[y]=f[x]+num[y]; g[y]=g[x];//因为只能从x到y } else if(f[y]==f[x]+num[y]) { g[y]+=g[x]; g[y]%=mod; } //处理度数 du[y]--; if(!du[y]) q.push(y); } } } int main() { cin>>n>>m>>mod; fu(i,1,m) { int u,v; cin>>u>>v; to[u].push_back(v); } fu(i,1,n) { if(dfn[i]==0) tarjan(i); } //按颜色建图 fu(i,1,n) { for(int j:to[i]) { int u=col[i],v=col[j]; if(u==v||used[u][v]) continue; used[u][v]=1; go[u].push_back(v); du[v]++; } } topusort(); ll mx=0,ans=0;//记录最长的链和方法数 fu(i,1,point) { mx=max(mx,f[i]); } fu(i,1,point) { if(mx==f[i]) ans+=g[i],ans%=mod; } printf("%lld\n",mx); printf("%lld\n",ans); return 0; }