[ZJOI2007]最大半连通子图

题目描述:

给你一个图G,让你求最大半连通子图拥有的节点数K,以及不同的最大半联通子图的数量C,C要对X取模

思路:

这是一个比较复杂的题?

主要思路是:tarjan缩点+拓扑排序+dp

首先使用tarjan缩点,得到一个有向无环图,缩点的时候需要记录每个点集有的点数,

u和v都是缩点后的点,且有u指向v的边,val指的是点集中的点数,f[i] 数组表示从起点出发到 i 的最大半连通子图的数量,

使用拓扑排序,从入度为0的点开始,使用dp,有两种情况:

  • ​,此时dp[v]的值就可以修改了,dp[v] = dp[u] + val[v], f[v] = f[u],
  • dp[u] + val[v] = dp[v], 此时dp[v]的值不需要修改,f[v] += f[u]

此时我们只需要循环缩点后的点集,找出最大的dp值,这就是G的最大半连通子图拥有的节点数K ,对于所有dp = K的点的f数组的值加起来就是不同的最大半连通子图的数目C

有一些小的细节方面:

  • 缩点后建图的时候会建出重边,所以dp的时候要判断一下
  • 记得取模
  • 数组开的要足够大
  • dp记得初始化
#include<map>
#include<set>
#include<stack>
#include<queue>
#include<cmath>
#include<cstdio>
#include<string>
#include<vector>
#include<sstream>
#include<cstring>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
using namespace std;
#define eps 1.0E-8
#define endl '\n'
//#define inf 0x3f3f3f3f
#define MAX  2000000 + 50
//#define mod 1000000007
#define lowbit(x) (x & (-x))
#define sd(n) scanf("%d",&n)
#define sdd(n,m) scanf("%d %d",&n,&m)
#define pd(n) printf("%d\n", (n))
#define pdd(n,m) printf("%d %d\n",n, m)
#define sddd(n,m,z) scanf("%d %d %d",&n,&m,&z)
#define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define mem(a,b) memset((a),(b),sizeof(a))
#define max(a,b) (((a)>(b)) ? (a):(b))
#define min(a,b) (((a)>(b)) ? (b):(a))
typedef  long long ll ;
typedef unsigned long long ull;
//不开longlong见祖宗!不看范围见祖宗!
inline int IntRead(){char ch = getchar();int s = 0, w = 1;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0';ch = getchar();}return s * w;}

int n, m, mod;
int x, y;

int tot;//链式前向星建原图
int head[MAX];
struct ran{
    int  to, nex;
}tr[MAX];
void add(int u, int v){
    tr[++tot].to = v;
    tr[tot].nex = head[u];
    head[u] = tot;
}

int tim, cnt;//tim是时间戳,cnt是缩点数
int dfn[MAX], low[MAX];
int val[MAX];//记录每个点集的点的数量
stack<int>st;
int color[MAX];
bool vis[MAX];
void tarjan(int u){//板子
    dfn[u] = low[u] = ++tim;
    vis[u] = 1;
    st.push(u);
    for(int i = head[u]; i; i = tr[i].nex){
        int v = tr[i].to;
        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]){
        int now;
        ++cnt;
        do {
            now = st.top();
            st.pop();
            vis[now] = 0;
            color[now] = cnt;
            ++val[cnt];
        } while (now != u);
    }
}

int ttot;//缩点后的链式前向星
int thead[MAX];
ran ttr[MAX];
void tadd(int u, int v){
    ttr[++ttot].to = v;
    ttr[ttot].nex = thead[u];
    thead[u] = ttot;
}

int maxn;//输出的第一个值
int in[MAX];//点集的入度
int dp[MAX];
int f[MAX];
int pos[MAX];//判断是否是重边

void topo(){
    queue<int>q;
    for(int i = 1; i <= cnt; ++i){
        dp[i] = val[i];//初始化
        f[i] = 1;
        if(!in[i])q.push(i);
    }
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        for(int i = thead[u]; i; i = ttr[i].nex){
            int v = ttr[i].to;
            --in[v];
            if(!in[v])q.push(v);
            if(pos[v] == u)continue;//判重边
            if(dp[u] + val[v] > dp[v]){
                dp[v] = dp[u] + val[v];
                f[v] = f[u];
            }
            else if(dp[u] + val[v] == dp[v]){
                f[v] = (f[v] + f[u]) % mod;
            }
            pos[v] = u;
        }
    }
    int maxn = 0;
    ll ans = 0;
    for(int i = 1; i <= cnt; ++i)maxn = max(maxn, dp[i]);
    for(int i = 1; i <= cnt; ++i){
        if(dp[i] == maxn)ans = (ans + f[i]) % mod;
    }
    cout<<maxn<<endl<<ans<<endl;
}

int main(){
    sddd(n, m, mod);
    for(int i = 1; i <= m; ++i){
        sdd(x, y);
        add(x, y);
    }
    for(int i = 1; i <= n; ++i){
        if(!dfn[i])tarjan(i);
    }
    for(int u = 1; u <= n; ++u){
        for(int j = head[u]; j; j = tr[j].nex){
            int v = tr[j].to;
            if(color[u] != color[v]){
                ++in[color[v]];
                tadd(color[u], color[v]);
            }
        }
    }
    topo();

    return 0;
}