Description

题库链接

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

\(n\leq 10^5,n-1\leq m\leq n+10\)

Solution

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

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

不过这样时间复杂度是 \(O\left(n\cdot 2^{2(m-n+1)}\right)\) 的,并且空间也会爆。

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

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

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

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

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

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

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

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

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

\[\begin{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\) 是 \(O\left(2(m-n+1)\cdot 2^{2(m-n+1)}\right)\) 。

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\n", ans);
}
int main() {work(); return 0; }

[HNOI 2018]毒瘤的更多相关文章

  1. 【HNOI 2018】毒瘤

    Problem Description 从前有一名毒瘤. 毒瘤最近发现了量产毒瘤题的奥秘.考虑如下类型的数据结构题:给出一个数组,要求支持若干种奇奇怪怪的修改操作(例如给一个区间内的数同时加上 \(c ...

  2. 【HNOI 2018】转盘

    Problem Description 一次小 \(G\) 和小 \(H\) 原本准备去聚餐,但由于太麻烦了于是题面简化如下: 一个转盘上有摆成一圈的 \(n\) 个物品(编号 \(1\) 至 \(n ...

  3. HNOI 2018 简要题解

    寻宝游戏 毒瘤题. 估计考试只会前30pts30pts30pts暴力然后果断走人. 正解是考虑到一个数&1\&1&1和∣0|0∣0都没有变化,&0\&0& ...

  4. [HNOI 2018]道路

    Description 题库链接 给出一棵含有 \(n\) 个叶子节点的二叉树,对于每个非叶子节点的节点,其与左儿子相连的边为公路,其与右儿子相连的边为铁路.对于每个节点,选择一条与其儿子相连的铁路或 ...

  5. [HNOI 2018]游戏

    Description 题库链接 有 \(n\) 个房间排成一列,编号为 \(1,2,...,n\) ,相邻的房间之间都有一道门.其中 \(m\) 个门上锁,其余的门都能直接打开.现在已知每把锁的钥匙 ...

  6. [HNOI 2018]排列

    Description 题库链接 给定 \(n\) 个整数 \(a_1, a_2, \dots, a_n, 0 \le ai \le n\) ,以及 \(n\) 个整数 \(w_1, w_2, \do ...

  7. [HNOI/AHOI2018]毒瘤

    题目描述 https://www.lydsy.com/JudgeOnline/upload/201804/%E6%B9%96%E5%8D%97%E4%B8%80%E8%AF%95%E8%AF%95%E ...

  8. 【HNOI 2018】排列

    Problem Description 给定 \(n\) 个整数 \(a_1, a_2, \ldots , a_n(0 \le a_i \le n)\),以及 \(n\) 个整数 \(w_1, w_2 ...

  9. 【HNOI 2018】游戏

    Problem Description 一次小 \(G\) 和小 \(H\) 在玩寻宝游戏,有 \(n\) 个房间排成一列,编号为 \(1,2,-,n\),相邻房间之间都有 \(1\) 道门.其中一部 ...

随机推荐

  1. Lambda 笔记

    lambda表达式,将会带来代码的灵活性,同时使我们的代码更具表现力. Dim doubleIt As Func(Of Integer, Integer) = _ Function(x As Inte ...

  2. SharePoint 2013 搜索SharePoint 特定列和特定文档(自己定义搜索)

    SharePoint 2013 搜索SharePoint 特定列和特定文档 1,操作步骤和图例,因语言和版本号的不同 我尽量使用抓图方式. 2.  In Central Administration, ...

  3. iOS开发加快审核app

    实现app加快审核的步骤: 1.https://developer.apple.com/contact/进入这个网址点击App Store下的Expediting an App Review进入这个页 ...

  4. /bin/sh 与 /bin/bash 的区别

    /bin/sh 与 /bin/bash 的区别,用 : 截取字符串不是POSIX 标准的. 区别 sh 一般设成 bash 的软链 (symlink) ls -l /bin/sh lrwxrwxrwx ...

  5. 201521123083《Java程序设计》第二周学习总结

    [TOC] 1. 本周学习总结 这周我花在java里面的时间就是在做pta和看课本继承,接口和多态这几章的内容. 在pta上的总结: 详细的具体在后面pta实验中总结再说,这里先说几点. 借着List ...

  6. Spring Cloud Commons模块

    上一篇介绍了 Spring Cloud Context模块 ,本文介绍SpringCloud的另一个基础模块 SpringCloud Commons模块 .只要在项目的pom文件中引入了spring- ...

  7. Github远程推送一直Everything up-to-date

    问题描述: Github远程推送一直Everything up-to-date,但其实并没有推送成功,远程库中没有更新文件 可能原因分析及解决方法: "git push with no ad ...

  8. 自学工业控制网络之路1.4-典型的现场总线介绍CAN

    返回 自学工业控制网络之路 自学工业控制网络之路1.4-典型的现场总线介绍CAN 1991年3月,发布了CAN技术贵干v2.0,包含了A.B两部分.CAN2.0A给出报文标准格式,CAN2.0B给出了 ...

  9. 我发起了一个 .Net 平台上的 开源项目 知识图谱 Babana Map 和 文本文件搜索引擎 Babana Search

    起因 也是 前几天 有 网友 在 群 里发了   知识图谱   相关的文章, 还有 有 网友 问起   NLog -> LogStash -> Elastic Search  的 问题, ...

  10. 自然语言交流系统 phxnet团队 创新实训 个人博客 (十)

    下载emacs-23.1.tar.gz http://ftp.gnu.org/pub/gnu/emacs/emacs-23.1.tar.gz cd /opt    //cp emacs-23.1.ta ...