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

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 answer2
void 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值