P2272 [ZJOI2007]最大半连通子图
Problem
当一个有向图\(G= (V,E)\)满足:\(\forall u,v \in V,u \to v\)或\(v \to u\)。
求这个图的最大半联通子图(节点数最多)节点数和最大半联通子图个数模\(X\)的值。
\(n \le 10^5,m \le 10^6,X \le 10^8\)
Solution
显然SCC也是强连通子图。所以先用Tarjan缩点,然后将图变成一个DAG(套路)。问题转变成:求缩点完后的DAG中的最长链和最长链个数。显然可以拓扑+dp。
注意:缩点后会出现重边,要注意不能让重边加入,用个map记录即可。
# include <bits/stdc++.h>
using namespace std;
# define int long long
const int N = 1e5 + 5;
int n,m,X;
vector <int> g[N],g2[N];
vector <pair<int,int> > G;
int dfn[N],low[N],dfntot = 0,s[N],top = 0,scc_tot = 0,siz[N],belong[N],du[N];
bool vis[N]; int dis[N],cnt[N];
map<int,map<int,int> > check;
void dfs(int x)
{
dfn[x] = low[x] = ++dfntot;
s[++top] = x; vis[x] = 1;
for(int i = 0; i < (int)g[x].size(); i++)
{
int v = g[x][i];
if(!dfn[v])
{
dfs(v); low[x] = min(low[x],low[v]);
}
else if(vis[v]) low[x] = min(low[x],dfn[v]);
}
if(dfn[x] == low[x])
{
int temp = 0;
++scc_tot;
do
{
temp = s[top--];
++siz[scc_tot]; belong[temp] = scc_tot;
vis[temp] = 0;
}while(x != temp);
}
return;
}
void topsort(void)
{
queue <int> q;
for(int i = 1; i <= scc_tot; i++)
{
if(du[i] == 0) {q.push(i); dis[i] = siz[i],cnt[i] = 1;}
}
while(!q.empty())
{
int x = q.front(); q.pop();
for(int i = 0; i < (int)g2[x].size(); i++)
{
int v = g2[x][i];
if(siz[v] + dis[x] > dis[v])
{
dis[v] = dis[x] + siz[v];
cnt[v] = cnt[x];
}
else if(siz[v] + dis[x] == dis[v])
{
cnt[v] = (cnt[v] + cnt[x]) % X;
}
if(--du[v] == 0) q.push(v);
}
}
return;
}
signed main(void)
{
scanf("%lld%lld%lld",&n,&m,&X);
for(int i = 1; i <= m; i++)
{
int a,b; scanf("%lld%lld",&a,&b);
g[a].push_back(b);
G.push_back(make_pair(a,b));
}
for(int i = 1; i <= n; i++)
{
if(!dfn[i]) dfs(i);
}
for(int i = 0; i < m; i++)
{
int u = belong[G[i].first],v = belong[G[i].second];
if(u != v && !check[u][v]) g2[u].push_back(v),du[v]++,check[u][v] = 1;
}
topsort();
int ans1 = 0,ans2 = 0;
for(int i = 1; i <= scc_tot; i++)
{
if(ans1 < dis[i]) {ans1 = dis[i]; ans2 = cnt[i] % X;}
else if(ans1 == dis[i]) {ans2 = (ans2 + cnt[i]) % X;}
}
printf("%lld\n%lld",ans1 % X,ans2);
return 0;
}