luogu P2272 [ZJOI2007] 最大半连通子图

原题链接1
原题链接2

题意

最大半连通图:对于图中任意两点 \(u\)\(v\) 存在一条 \(u\)\(v\) 的有向路径 或者\(v\)\(u\) 的有向路径,都有也行。

求一个图中不同的最大半连通子图的大小与数目。

思路

主思路:Tarjan,缩点,dp + 计数。

如果两点在一个 scc 内,则它们必然是半连通子图,为了方便计算,考虑 Tarjan 求强连通分量,缩点并重建一个 DAG。

在这个 DAG 中,半连通子图都是一条链,否则若有分支,必有至少两点无法抵达互相。所以 DAG 中最长链的长度,即为最大半连通子图的大小。而不同的最大半连通子图的数目就是最长链个数。

在求最长链的时候,考虑 dp 递推做法,同时需要在发现链长同为当前最优时另外计数。

代码:

注意:

  1. dp 倒序递推;
  2. 原图 scc 之间可能有重边,注意判重;
  3. 旧图新图之间注意区分变量名。
#include<bits/stdc++.h>
#define RG register
#define IL inline
using namespace std;
const int maxn = 100005;
const int maxm = 1000006;
IL int Read(); IL void Rite(int x);
struct node{
  int fm,to,nxt;
}edge[maxm];
struct Node{
  int fm,to,nxt;
}Edge[maxm];
int n,m,x,mo;
int head[maxm],cnt;
int dfn[maxn],low[maxn],ins[maxn];
int ti,scc;
stack<int> stk;
int nme[maxn],siz[maxn]; 
int Head[maxm],Cnt;
int dp[maxn],num[maxn];
int vis[maxn],ans,tmp;

IL void input(int u,int v){
  edge[cnt] = {u,v,head[u]};
  head[u] = cnt++;
}
void Input(int u,int v){
  Edge[Cnt] = {u,v,Head[u]};
  Head[u] = Cnt++; 
}

IL void Tarjan(int u){
  low[u] = dfn[u] = ++ti;
  stk.push(u); ins[u] = 1;
  for(RG int i = head[u];~i;i = edge[i].nxt){
  	int v = edge[i].to;
  	if(!dfn[v]){
  	  Tarjan(v);
  	  low[u] = min(low[u],low[v]);
	} 
	else if(ins[v])
	  low[u] = min(low[u],dfn[v]);
  }
  if(low[u] == dfn[u]){
	scc++;
	do{
	  x = stk.top();	  	  	
	  stk.pop();
	  nme[x] = scc;	  
	  ins[x] = 0;
	  siz[scc]++;
	}while(u != x);
  }
}

int main(){
  memset(head,-1,sizeof(head));
  memset(Head,-1,sizeof(Head));
  n = Read(); m = Read(); mo = Read();
  for(RG int i = 1;i <= m;++i){
  	int a = Read(),b = Read();
  	input(a,b);
  }

  for(RG int i = 1;i <= n;++i)
    if(!dfn[i]) Tarjan(i);
  for(RG int u = 1;u <= n;++u){
  	for(RG int i = head[u];~i;i = edge[i].nxt){
	  int v = edge[i].to;
	  if(nme[u] != nme[v])
	    Input(nme[u],nme[v]);	
	}
  }

  for(RG int i = 1;i <= scc;++i)
    num[i] = 1,dp[i] = siz[i];
  for(RG int u = scc;u >= 1;--u){
  	for(RG int i = Head[u];~i;i = Edge[i].nxt){
	  int v = Edge[i].to;
	  if(vis[v] == u) continue;
      //注意判重,不能用 bool
	  vis[v] = u;
	  if(dp[v] < dp[u] + siz[v]){
	  	dp[v] = dp[u] + siz[v];
	  	num[v] = num[u];
	  }
	  else if(dp[v] == dp[u] + siz[v])
	  	num[v] = (num[u] + num[v]) % mo;
	}
  }
  for(RG int i = 1;i <= scc;++i){//注意倒序
  	if(dp[i] > ans)//求解最优
	  ans = dp[i],tmp = num[i];	
	else if(dp[i] == ans)//统计数目
	  tmp = (tmp + num[i]) % mo;
  }

  Rite(ans);
  putchar('\n');
  Rite(tmp);
  return 0;
}
IL int Read(){
  char c = getchar();
  int x = 0,f = 1;
  while(c < '0' || c > '9'){
    if(c == '-') f = -1;
    c = getchar();
  }
  while(c >= '0' && c <= '9'){
    x = x * 10 + c - '0';
    c = getchar();
  }
  return x * f;
}
IL void Rite(int x){
  if(x < 0) putchar('-'),x = -x;
  if(x > 9) Rite(x / 10);
  putchar(x % 10 + '0');
}

真毒瘤,妈的。

posted @ 2023-08-23 16:40  CultReborn  阅读(40)  评论(0)    收藏  举报