zoukankan      html  css  js  c++  java
  • [HNOI 2018]毒瘤

    Description

    题库链接

    给出一个 (n) 个点 (m) 条边的无向图。求独立集个数。

    (nleq 10^5,n-1leq mleq n+10)

    Solution

    由于返祖边数目很少,比较容易想到把树搞出来然后状压返祖边相连的两个点。需要状压的点的总数最多是 (2(m-n+1)=22) 个,假设这些点叫做“关键点”。

    考虑记 (f_{u,S}) 表示在以 (u) 为根的子树中选中关键点的状态为 (S) 的方案数。

    不过这样时间复杂度是 (Oleft(ncdot 2^{2(m-n+1)} ight)) 的,并且空间也会爆。

    不过注意到这样的一个问题:假设一条返祖边为 (u ightarrow v) ,如果对于整棵树上的关键点的选取状态 (s_1,s_2) , 如果 (u, v)(s_1,s_2) 中的状态是相同的,那么一定在原树中 (u ightarrow v) 的路径上的节点选取的方案数是相同的。

    换句话说就是整棵树的节点的选取,起决定性因素的就是这些“关键点”。

    考虑构出一颗包含这些关键点的虚树,我们只要在最外层状压关键点的选取状态,然后按照这个状态在虚树上做一次类似的 (DP) 就好了。

    不过要在虚树上做 (DP) ,我们需要预处理出刚才所说的不在虚树中的节点的选取状态方案数。

    注意到不在虚树上的节点有如下两种情况:

    1. 在虚树上的节点的子树中;
    2. 在虚树的路径上(包括路径上的点的子树内)。

    注意到如果原树的一棵子树中没有虚树上的节点,那么这棵子树中的节点的选取是不受约束的,那么我们可以按照求树上独立集的方案来求。

    对于虚树上的路径,我们考虑预处理出一个 (k_{0/1,0/1}) ,表示 (u ightarrow v) 这条路径上 (u) 的儿子选或不选, (v) 选或不选的方案数。其实这个可以用上述相同的方法来求,具体的话只要遍历 (u, v) 间的所有点即可。

    (f_{u,1/0}) 表示转移到 (u) 这个节点时,当前点选或不选的方案数。转移方程就是,边 (u ightarrow v)

    [egin{aligned}f_{u,0}&=f_{u,0}cdot (k_{0,0}f_{v,0}+k_{0,1}f_{v,1})\f_{u,1}&=f_{u,1}cdot (k_{0,0}f_{v,0})end{aligned}]

    复杂度的话预处理是 (O(n)) 的,虚树上 (dp)(Oleft(2(m-n+1)cdot 2^{2(m-n+1)} ight))

    A 完 hnoi2018 ,光荣(个屁)退役

    Code

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 100000+20, yzh = 998244353;
    
    int n, m, u, v, lim, fa[N][20], dep[N], dfn[N], idx;
    int ks[N], kt[N], cnt, st[N], tot, lst[N], id[N], tol;
    int S[N], top, vis[N], dp[N][2], f[N][2], ch[N], bin[30];
    struct data {
        int k0, k1;
        data(int _k0 = 0, int _k1 = 0) {k0 = _k0, k1 = _k1; }
        data operator + (const data &b) const {return data((k0+b.k0)%yzh, (k1+b.k1)%yzh); }
        data operator * (const int &b) const {return data(1ll*b*k0%yzh, 1ll*b*k1%yzh); }
        int F(int x, int y) {return (1ll*x*k0%yzh+1ll*y*k1%yzh)%yzh; }
    }k[N][2];
    struct graph {
        struct tt {int to, next, tag; }edge[N<<1];
        int path[N], top;
        graph() {memset(path, top = -1, sizeof(path)); }
        void add(int u, int v) {edge[++top] = (tt){v, path[u]}; path[u] = top; }
        void dfs1(int u, int father, int depth) {
            dep[u] = depth; fa[u][0] = father; dfn[u] = ++idx;
            for (int i = 1; i <= lim; i++) fa[u][i] = fa[fa[u][i-1]][i-1];
            for (int i = path[u]; ~i; i = edge[i].next)
                if (edge[i].to != father) {
                    if (dfn[edge[i].to] == 0) dfs1(edge[i].to, u, depth+1);
                    else if (dep[edge[i].to] > dep[u]) ks[++cnt] = u, kt[cnt] = edge[i].to;
                    else edge[i].tag = edge[i^1].tag = 1;
                }
        }
        int get_lca(int u, int v) {
            if (dep[u] < dep[v]) swap(u, v);
            for (int i = lim; i >= 0; i--) if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
            if (u == v) return u;
            for (int i = lim; i >= 0; i--) if (fa[u][i]^fa[v][i]) u = fa[u][i], v = fa[v][i];
            return fa[u][0];
        }
        void dfs2(int u) {
            f[u][0] = dp[u][0], f[u][1] = dp[u][1];
            for (int i = path[u], v; ~i; i = edge[i].next) {
                dfs2(v = edge[i].to);
                int f0 = k[v][0].F(f[v][0], f[v][1]);
                int f1 = k[v][1].F(f[v][0], f[v][1]);
                f[u][0] = 1ll*f[u][0]*(f0+f1)%yzh;
                f[u][1] = 1ll*f[u][1]*f0%yzh;
            }
            if (ch[u] != -1) f[u][ch[u]^1] = 0;
        }
    }g1, g2;
    bool comp(const int &a, const int &b) {return dfn[a] < dfn[b]; }
    void cal(int u) {
        dp[u][0] = dp[u][1] = 1;
        for (int i = g1.path[u], v; ~i; i = g1.edge[i].next)
            if ((v = g1.edge[i].to)^1) {
                if (dep[v] < dep[u] || vis[v]) continue;
                cal(v);
                dp[u][0] = 1ll*dp[u][0]*(dp[v][0]+dp[v][1])%yzh;
                dp[u][1] = 1ll*dp[u][1]*dp[v][0]%yzh;
            }
    }
    void getit(int t, int s) {
        int u = s; k[s][0] = data(1, 0); k[s][1] = data(0, 1);
        while (fa[u][0]^t) {
            vis[fa[u][0]] = 1; cal(fa[u][0]);
            data t = k[s][0];
            k[s][0] = (k[s][0]+k[s][1])*dp[fa[u][0]][0];
            k[s][1] = t*dp[fa[u][0]][1];
            u = fa[u][0];
        }
    }
    void dfs(int u) {
        for (int i = g2.path[u]; ~i; i = g2.edge[i].next) {
            dfs(g2.edge[i].to); getit(u, g2.edge[i].to);
        }
        dp[u][0] = dp[u][1] = 1;
        for (int i = g1.path[u], v; ~i; i = g1.edge[i].next)
            if ((g1.edge[i].tag)^1) {
                v = g1.edge[i].to;
                if (dep[v] < dep[u] || vis[v]) continue;
                cal(v);
                dp[u][0] = 1ll*dp[u][0]*(dp[v][0]+dp[v][1])%yzh;
                dp[u][1] = 1ll*dp[u][1]*dp[v][0]%yzh;
            }
    }
    
    void work() {
        scanf("%d%d", &n, &m); lim = log(n)/log(2);
        for (int i = 1; i <= m; i++) {
            scanf("%d%d", &u, &v); g1.add(u, v), g1.add(v, u);
        }
        g1.dfs1(1, 0, 1);
        for (int i = 1; i <= cnt; i++) st[++tot] = ks[i], st[++tot] = kt[i];
        sort(st+1, st+1+tot, comp); tot = unique(st+1, st+1+tot)-st-1;
        for (int i = 1; i <= tot; i++) id[st[i]] = i-1, lst[i] = st[i];
        lst[tol = tot+1] = 1;
        sort(lst+1, lst+1+tol, comp); tol = unique(lst+1, lst+1+tol)-lst-1;
        for (int i = 1; i <= tol; i++) vis[lst[i]] = 1;
        S[++top] = lst[1];
        for (int i = 2; i <= tol; i++) {
            int lca = g1.get_lca(S[top], lst[i]); vis[lca] = 1;
            while (dep[lca] < dep[S[top]]) {
                if (dep[S[top-1]] <= dep[lca]) {
                    g2.add(lca, S[top]), --top;
                    if (lca != S[top]) S[++top] = lca;
                    break;
                }
                g2.add(S[top-1], S[top]), --top;
            }
            if (lst[i] != S[top]) S[++top] = lst[i];
        }
        while (top > 1) g2.add(S[top-1], S[top]), --top;
        dfs(1);
        bin[0] = 1; for (int i = 1; i <= tot; i++) bin[i] = (bin[i-1]<<1);
        memset(ch, -1, sizeof(ch));
        int ans = 0;
        for (int s = 0; s < bin[tot]; s++) {
            bool flag = 1;
            for (int i = 1; i <= cnt; i++)
                if ((bin[id[ks[i]]]&s) && (bin[id[kt[i]]]&s)) {flag = 0; break; }
            if (flag == 0) continue;
            for (int i = 1; i <= tot; i++) ch[st[i]] = bool(s&bin[i-1]);
            g2.dfs2(1); (ans += (f[1][0]+f[1][1])%yzh) %= yzh;
        }
        printf("%d
    ", ans);
    }
    int main() {work(); return 0; }
  • 相关阅读:
    perl文本输出对齐
    putty配色方案
    java线程 同步与异步 线程池
    android为什么不允许新开启一个线程来更新UI,而是用handler来更新界面
    真正能获得基站LBS定位的android程序包括GSM、CDMA
    Android之TelephonyManager&GsmCellLocation类的方法详解
    网络编程之同步,阻塞,异步,非阻塞
    Android私有文件资源文件的存取
    [转]android 获取手机GSM/CDMA信号信息
    json格式转换
  • 原文地址:https://www.cnblogs.com/NaVi-Awson/p/8966893.html
Copyright © 2011-2022 走看看