[ZJOI2007]最大半连通子图

感谢旭爷带我打省队康复赛。
开始看到这个题的时候,以为是一个拓扑然后\(dp\)的玩意,对每个点记录有多少条内向边和多少条外向边,如果一个点是包含于一个合法的半联通子图里,那和他相连的点一定要满足这个限制:
如果这个合法点往半联通子图的一个点只有一个方向的边,那么这个相连的点和合法点的边一定要和这条边方向一样。
如果往半联通子图的一个点有两条边,那么如果这个合法点在半联通子图里有一个点无法从这个点到合法点,那么这个相连的点一定要有一条指向合法点的边,对应的,有另外一个限制,这个合法点一定要有一条指向相连点的边。
然后发现,条件好多完全没有头绪维护的样子。
然后手玩了几组图,发现第二个条件等于在这个半联通子图有一个强连通分量,把这些联通分量缩点后,那么缩完点后的这些点都满足第一个条件。
第一个条件等价于,选出来的是一条链。
所以变成了强连通分量缩点,找最长链。
拓扑加上一个\(dp\)就行了。
注意两个联通分量间只连一条边。
(听说因为只有两题,第二题比较会乱搞,所以拿了一个不错的分)

[ZJOI2007]最大半连通子图
// code by fhq_treap
#include <bits/stdc++.h>
#define ll long long
#define N 2000005
#define M 10000007

inline ll read() {
    char C = getchar();
    ll A = 0, F = 1;

    while (('0' > C || C > '9') && (C != '-'))
        C = getchar();

    if (C == '-')
        F = -1, C = getchar();

    while ('0' <= C && C <= '9')
        A = (A << 1) + (A << 3) + (C - 48), C = getchar();

    return A * F;
}

struct P {
    int to, next;
};


struct Map {
    ll cnt, head[N];
    Map() {
        cnt = 0;
        std::memset(head, 0, sizeof(head));
    }
    P e[N];
    inline void add(int x, int y) {
        e[++cnt].to = y;
        e[cnt].next = head[x];
        head[x] = cnt;
    }
} A, T;

ll dfncnt, sccnt = 0, dfn[N], low[N], scc[N], siz[N];

std::stack<int>QWQ;

bool vis[N];

std::set<ll>QAQ;

inline void tarjan(ll u) {
    dfn[u] = low[u] = ++ dfncnt;
    QWQ.push(u);
    //  std::cout<<u<<std::endl;
    vis[u] = 1;

    for (int i = A.head[u]; i; i = A.e[i].next) {
        ll v = A.e[i].to;

        if (!dfn[v]) {
            tarjan(v);
            low[u] = std::min(low[u], low[v]);
        } else if (vis[v])
            low[u] = std::min(low[u], dfn[v]);
    }

    //  std::cout<<u<<" "<<dfn[u]<<" "<<low[u]<<std::endl;
    if (dfn[u] == low[u]) {
        ++sccnt;

        while (QWQ.top() != u) {
            scc[QWQ.top()] = sccnt;
            siz[sccnt] ++ ;
            vis[QWQ.top()] = 0;
            QWQ.pop();
        }

        siz[sccnt] ++ ;
        scc[QWQ.top()] = sccnt;
        vis[QWQ.top()] = 0;
        QWQ.pop();
    }
}

ll n, m, mod;

ll ans = 0, ansc = 0, in[N];

ll f[N], g[N]; //值 方案数

bool use[N];

inline void top(int u) {
    use[u] = 1;

    //  std::cout<<u<<" "<<f[u]<<" "<<g[u]<<std::endl;
    for (int i = T.head[u]; i; i = T.e[i].next) {
        int v = T.e[i].to;
        in[v] -- ;

        //      std::cout<<v<<" "<<f[v]<<std::endl;
        if (f[v] < f[u] + siz[v]) {
            f[v] = f[u] + siz[v];
            g[v] = g[u];
        } else {
            if (f[v] == f[u] + siz[v])
                g[v] = (g[v] + g[u]) % mod;
        }

        if (!in[v])
            top(v);
    }
}

int main() {
    n = read(), m = read(), mod = read();

    for (int i = 1; i <= m; ++i) {
        int x = read(), y = read();
        A.add(x, y);
    }

    for (int i = 1; i <= n; ++i)
        if (!dfn[i])
            tarjan(i);

    //  for(int i = 1;i <= n;++i)
    //  std::cout<<scc[i]<<std::endl;
    for (int i = 1; i <= n; ++i) {
        for (int j = A.head[i]; j; j = A.e[j].next) {
            int v = A.e[j].to;

            if (scc[i] != scc[v])
                if (!QAQ.count(scc[i] * 100000ll + scc[v]))
                    QAQ.insert(scc[i] * 100000ll + scc[v]), T.add(scc[i], scc[v]), in[scc[v]] ++ ;
        }
    }

    //  for(int i = 1;i <= sccnt;++i)
    //  std::cout<<siz[i]<<std::endl;
    for (int i = 1; i <= sccnt; ++i) {
        if (!use[i] && !in[i])
            f[i] = siz[i], g[i] = 1, top(i);
    }

    for (int i = 1; i <= sccnt; ++i) {
        if (f[i] > ans)
            ans = f[i];
    }

    for (int i = 1; i <= sccnt; ++i)
        if (f[i] == ans)
            ansc = (ansc + g[i]) % mod;

    std::cout << ans << std::endl << ansc % mod << std::endl;
}
posted @ 2021-04-26 11:16  fhq_treap  阅读(71)  评论(0编辑  收藏  举报