BZOJ 1093: [ZJOI2007]最大半连通子图 强连通分量缩点,最长链,拓扑排序,DP

来源:互联网 发布:播音录音软件 编辑:程序博客网 时间:2024/04/29 03:18

Description

  一个有向图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的余数。
Input

  第一行包含两个整数N,M,X。N,M分别表示图G的点数与边数,X的意义如上文所述接下来M行,每行两个正整
数a, b,表示一条有向边(a, b)。图中的每个点将编号为1,2,3…N,保证输入中同一个(a,b)不会出现两次。N ≤1
00000, M ≤1000000;对于100%的数据, X ≤10^8
Output

  应包含两行,第一行包含一个整数K。第二行包含整数C Mod X.
Sample Input
6 6 20070603

1 2

2 1

1 3

2 4

5 6

6 4
Sample Output
3

3

解题方法: Tarjan求scc,在缩点后的图跑拓排求最长链。在拓排树进行dp。拓排针对层级问题进行,先处理完了一个节点的前驱在处理该节点,除去了后效性,故可以在拓排树上dp。第一次知道拓排还可以这样用2333,dp[i]表示到i的最大半连通子图,sl[i]表示i这个强连通分量的点数,sum[i]表示方案数%k。

dp[v]=max(dp[u]+s[v])uv

sum[v]=sigmasum[u]dp[u]+s[v]==dp[v]

这里要注意,两点间可能有重边,要做判断防止一个点被加两遍。

代码如下:

#include <bits/stdc++.h>using namespace std;const int maxn = 100005;const int maxm = 2000005;int ind, cnt, scc, top, head1[maxn], head2[maxn];int dfn[maxn], low[maxn], hav[maxn], belong[maxn], q[maxn];bool inq[maxn];int n, m, X;int du[maxn], dp[maxn], sum[maxn], vis[maxn];//判断重边struct edge{int v, nxt; } E1[maxm], E2[maxm];int ans1, ans2;//answer1 answer2void init(){    memset(head1, -1, sizeof(head1));    memset(head2, -1, sizeof(head2));    cnt = 0;}void add1(int u, int v){    E1[cnt].v = v, E1[cnt].nxt = head1[u], head1[u]= cnt++;}void add2(int u, int v){    E2[cnt].v = v, E2[cnt].nxt = head2[u], head2[u] = cnt++; du[v]++;}void tarjian(int u){ //有向图的强连通分量缩点    dfn[u] = low[u] = ++ind;    q[++top] = u; inq[u] = 1;    for(int i = head1[u]; ~i; i = E1[i].nxt){        int v = E1[i].v;        if(!dfn[v]) tarjian(v), low[u] = min(low[u], low[v]);        else if(inq[v]) low[u] = min(low[u], dfn[v]);    }    int now = 0;    if(low[u] == dfn[u]){        scc++;        while(now != u){            now = q[top]; top--;            inq[now] = 0;            hav[scc]++;            belong[now] = scc;        }    }}void rebuild(){ //重新建图    cnt = 0;    for(int x = 1; x <= n; x++){        for(int i = head1[x]; ~i; i = E1[i].nxt){            if(belong[x] != belong[E1[i].v]){                add2(belong[x], belong[E1[i].v]);            }        }    }}void topsort() //拓扑排序求最长链,递推记录方案{    queue <int> que;    while(!que.empty()) que.pop();    for(int i = 1; i <= scc; i++){        if(!du[i]){            que.push(i);        }        dp[i] = hav[i]; sum[i] = 1;    }    while(!que.empty()){        int u = que.front(); que.pop();        for(int i = head2[u]; ~i; i = E2[i].nxt){            int v = E2[i].v;            du[v]--;            if(du[v] == 0) que.push(v);            //if(v == u) continue;            if(vis[v] == u) continue;            if(dp[u] + hav[v] > dp[v]){                dp[v] = dp[u] + hav[v];                sum[v] = sum[u];            }            else if(dp[u] + hav[v] == dp[v]){                sum[v] = (sum[v] + sum[u]) % X;            }            vis[v] = u;        }    }}int main(){    init();    scanf("%d%d%d", &n, &m, &X);    for(int i = 1; i <= m; i++){        int u, v;        scanf("%d%d", &u, &v);        add1(u, v);    }    for(int i = 1; i <= n; i++) if(!dfn[i]) tarjian(i);    rebuild();    topsort();    for(int i = 1; i <= scc; i++){        if(dp[i] > ans1) ans1 = dp[i], ans2 = sum[i];        else if(dp[i] == ans1){            ans2 = (ans2 + sum[i]) % X;        }    }    cout << ans1 << endl;    cout << ans2 << endl;    return 0;}
0 0