首页 > 其他分享 >P8867 NOIP2022 建造军营

P8867 NOIP2022 建造军营

时间:2022-12-07 05:33:05浏览次数:100  
标签:选点 联通 答案 int times P8867 军营 NOIP2022 mod

P8867 NOIP2022 建造军营 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

给定一个无向联通图 \(G = (V', E')\),求有多少个二元组 \((V, E)\),满足:

  • \(V \subseteq V'\),\(E \subseteq E'\),\(V \ne \varnothing\)。
  • 在 \(G\) 上,断开 \(E’ - E\) 中任意一条边后,都有 \(V\) 中所有点在 \(G\) 上仍然联通。

35 pts

枚举 \(2^n - 1\) 种 \(V\) 的情况,再用 \(m(n +m)\) 的复杂度暴力检验将每条边断开后 \(V\) 是否仍然联通,记录【断开该边后 \(V\) 仍然可以联通】的边数 \(M\),答案累加 \(2^{M}\) 即可。

时间复杂度 \(\Theta(m(n+m)2^n)\),期望可以通过前 \(7\) 个数据点(没有实测)。

45 pts

考虑开特殊性质 \(\mathrm{A}\),也就是给定的图是一条链。

我们考虑当 \(V\) 最左面的点为 \(l\),最右面的点为 \(r\) 时,有多少种二元组。

当 \(l = r\),也就是 \(V\) 中只有一个元素 \(l\) 时,所有边都可以随便选,可以有 \(2^{n-1}\) 种取法。这一部分的答案是 \(n \times 2^{n-1}\)。

当 \(l \ne r\) 时,中间的 \(r-l\) 条边必须选入 \(E\),而两头的边都可以随便选取。此时 \(V\) 有 \(2^{r - l - 1}\) 种取法,\(E\) 有 \(2^{n-1-(r-l)}\) 种取法,惊喜发现这一部分答案就是 \(2^{n-2}\),和 \(l\),\(r\) 无关。

\(l\),\(r\) 的取法为 \(\dbinom{n}{2} = \dfrac{n(n-1)}{2}\),所以这部分的答案是 \(n(n-1)2^{n-3}\),总答案为 \(n \times 2 ^ {n-1} + n(n-1)2^{n-3}\)。

到这里都很送。

100 pts

不难发现,对于任意非桥边,删除它后整个图都可联通,因此所有非桥边在任意情况下都可以随便选。

这启发我们对整个图进行边双缩点。边双缩点后形成一棵树,原图非桥边将全部消失(被缩在一个点里),桥边变成新的树边。我们只需要在这个新树上讨论 \(V\) 和 \(E\) 的选取,最后再集体乘上 \(2^{m - M}\) 即可,这里 \(M\) 的含义是桥边的数量,也就是缩点后的树边数量。

计数问题要么是动态规划要么是排列组合,要么两个都占。接下来就不难想到树形 dp 了。我们记 \(T(u)\) 表示 \(u\) 的子树。

考虑设计状态,\(f(u)\) 表示 \(T(u)\) 的答案,也就是在 \(T(u)\) 上,最少选一个点,选任意条边的方案数。我们设 \(e(u)\) 表示 \(T(u)\) 中边的数量。

对于 \(u\) 的子节点 \(v\),如果我们决定不在 \(T(v)\) 上选点,那么 \(T(v)\) 上的边和 \((u, v)\) 这条边都有选和不选两种可能,有 \(2^{e(v) +1}\) 种情况。

若我们在 \(T(v)\) 上选点呢,出现问题:如果【\(T(v)\) 上选的所有点】不全部和【\(v\) 本身】通过【在 \(T(v)\) 上选过的边】联通,无论我们是否选择 \((u, v)\),都会存在 \(T(v)\) 上的点不能和 \(u\) 连通,此时 \(u\) 和其他子树上的点断然不可选择;否则,只要我们选择 \((u, v)\),那么 \(T(v)\) 上选的所有点就会全部和 \(u\) 连通。此时,\(u\) 和其他子树上的点就可合理选取。

换句话说,按照一般思路,我们此时应给状态多加一维,\(f(u, 1)\) 表示 \(T(u)\) 上至少选了一个点,且选的所有点全部和 \(u\) 通过所选边联通的方案数;\(f(u, 2)\) 表示 \(T(u)\) 上至少选了一个点,且存在一个点不能通过所选边和 \(u\) 联通的方案数;\(f(u, 3)\) 表示 \(T(u)\) 上不选点的方案数(不过显然有 \(f(u, 3) = 2^{e(u)}\),这个不需要动态规划求解),从而分开转移。最终答案是 \(f(1, 1) + f(1, 2)\)(令 \(1\) 为根)。

详细的转移方程和代码细节可以参考别的题解,因为在这里我要讲的是一种另外的思路,状态不需要多加一维,更有趣一点。

设 \(f(u)\) 表示 \(T(u)\) 上至少选了一个点,且选的所有点全部和 \(u\) 通过选边联通的方案数。其实就是上边的 \(f(u, 1)\),换句话说,我只需要 \(f(u, 1)\) 一个状态就可完成本题的全部转移和答案统计。

我们加设 \(c(u)\) 表示节点 \(u\) 代表原图上多少个点(即有多少个点被缩进 \(u\) 了)。对于 \(u\) 上的点,可随便选,所以会有个 \(2^{c(u)}\) 的系数。

接着讨论 \(u\) 的子节点 \(v\) 及其子树 \(T(v)\)。

  • \(T(v)\) 上不选点:那么 \(T(v)\) 上的边和 \((u, v)\) 这条边都有选和不选两种可能,有 \(2^{e(v) +1}\) 种情况;
  • \(T(v)\) 上选点:此时 \((u, v)\) 必选,有 \(f(v)\) 种情况。

最后还有个问题:\(f(u)\) 要保证 \(T(u)\) 上必选点。上面所有情况计算完毕,会包含所有不选点构成的 \(2^{e(u)}\) 种情况,最后减去。

得到 \(f(u)\) 的状态转移方程:

\[f(u) = -2^{e(u)} + 2 ^ {c(u)} \prod_{v \in \mathrm{child}(u)}2^{e(v) + 1}+f(v) \]

怎么做答案统计?

答案一定不是 \(f(1)\),因为当然会存在点不全部和树根联通的情况。

我们再考虑,一种选择方案,它选的所有点全都在 \(T(u)\) 上,还都和 \(u\) 通过所选边联通,方案数是多少?是 \(f(u)\) 吗?错,我没说所选边都在 \(T(u)\) 上:事实上,上面这个问题的答案是 \(f(u) \times 2^{M - e(u)}\)。

此时我想,答案是否为 \(f(1) +f(2) \times 2^{M - e(2)} + f(3) \times 2^{M- e(3)} + \cdots + f(M + 1) \times 2^{M + 1 - e(M+1)}\)(别忘了 $M +1 $ 是新树的点数,而且 \(M = e(1)\))?然后我构造了两个反例出来:

上面这个选择方案(红点和红边为选择对象,黑色的未不选的对象),会被 \(f(4) \times 2^{M - e(4)}\) 和 \(f(2) \times 2^{M - e(2)}\) 统计两次。

上面这个选择方案,会被 \(f(2) \times 2^{M - e(2)}\) 和 \(f(1)\) 统计两次。

是否有一种办法能让每种方案只被统计一次?

观察上面这两个反例和自己造的一些反例,不难发现,只在所选点集的 LCA 统计答案即可。也就是对于第一个反例,期望在 \(4\) 处统计该情况而不是在 \(2\);对于第二个反例,期望在 \(2\) 处该情况而不是在 \(1\)。如果在上面第二个反例的基础上,再选点 \(3\) 和 \((1, 3)\) 这条边,那就期望在 \(1\) 处统计答案了。

换句话讲,对于节点 \(u\),计算出满足所选点集的 LCA 为点 \(u\) 的方案数。

再换句话讲,就是计算出在 \(f(u)\) 中,要么是 \(u\) 本身被选择,要么是 \(u\) 的两棵及以上子树选了点的方案数,最后乘 \(2^{M - e(u)}\)。

再再换句话讲,就是计算出在 \(f(u)\) 中,\(u\) 没被选而且只有一个子树中选了点的方案数,最后再用 \(f(u)\) 减去它,乘上 \(2^{M - e(u)}\) 的系数。

我们枚举 \(u\) 的子节点 \(v\),\(T(v)\) 上有 \(f(v)\) 种方案,\((u, v)\) 必选(否则就不在 \(f(u)\) 里了),\(T(u)\) 中其他边可选可不选,总共是 \(f(v) \times 2^{e(u) - e(v) - 1}\) 种方案。

因此,\(u\) 作为 LCA 对答案的贡献是:

\[(f(u) - \sum_{v \in \mathrm{child}(u)} f(v) \times 2 ^ {e(u) - e(v) - 1}) \times 2^{M - e(u)} \]

时间复杂度 \(\Theta(n+m)\)。

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2022-12-07 04:15:55 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2022-12-07 05:23:10
 */
#include <bits/stdc++.h>
#define int long long
inline int read() {
    int x = 0;
    bool f = true;
    char ch = getchar();
    for (; !isdigit(ch); ch = getchar())
        if (ch == '-')
            f = false;
    for (; isdigit(ch); ch = getchar())
        x = (x << 1) + (x << 3) + ch - '0';
    return f ? x : (~(x - 1));
}
inline bool gmi(int &a, int b) {
    return b < a ? a = b, true : false;
}

const int maxn = (int)5e5 + 5;
const int maxm = (int)1e6 + 5;
std :: vector <int> G[maxn], T[maxn];

int dfn[maxn], low[maxn], snt = 0, times = 0, sno[maxn];
int c[maxn];
std :: stack <int> s;

void tarjan(int u, int fa) {
    low[u] = dfn[u] = ++times;
    s.push(u);

    for (int v : G[u]) {
        if (!dfn[v]) {
            tarjan(v, u);
            gmi(low[u], low[v]);
        } else if (v != fa)
            gmi(low[u], dfn[v]);
    }

    if (low[u] == dfn[u]) {
        ++snt;
        for (; ;) {
            int x = s.top();
            s.pop();
            sno[x] = snt;
            ++c[snt];
            if (x == u)
                break;
        }
    }
}

const int mod = (int)1e9 + 7;
int f[maxn], ans = 0;
int p[maxm], e[maxn];

void dp(int u, int fa) {
    f[u] = p[c[u]];
    for (int v : T[u]) {
        if (v == fa)
            continue;
        dp(v, u);
        (f[u] *= (p[e[v] + 1] + f[v])) %= mod;
        e[u] += e[v] + 1;
    }
    (f[u] += mod - p[e[u]]) %= mod;

    int now = f[u];
    for (int v : T[u]) {
        if (v == fa)
            continue;
        ((now -= f[v] * p[e[u] - e[v] - 1] % mod) += mod) %= mod;
    }
    (ans += now * p[snt - 1 - e[u]] % mod) %= mod;
}

signed main() {
    int n = read(), m = read();
    for (int _ = 1; _ <= m; ++_) {
        int u = read(), v = read();
        G[u].push_back(v);
        G[v].push_back(u);
    }

    tarjan(1, 0);
    
    for (int u = 1; u <= n; ++u)
        for (int v : G[u])
            if (sno[u] != sno[v])
                T[sno[u]].push_back(sno[v]);
    
    p[0] = 1;
    for (int i = 1; i <= m + 3; ++i)
        p[i] = (p[i - 1] << 1) % mod;

    dp(1, 0);
    printf("%lld\n", ans * p[m - e[1]] % mod);
    return 0;
}

如果觉得这篇题解写得好,请不要忘记点赞,谢谢!

标签:选点,联通,答案,int,times,P8867,军营,NOIP2022,mod
From: https://www.cnblogs.com/crab-in-the-northeast/p/luogu-p8867.html

相关文章

  • NOIP2022 T3 建造军营
    只有B国炸毁了图的割边,才会使得图不连通,进而可能会导致军营不连通。也就是说,A国可以随意地看守或不看守不是割边的边。因此想到边双缩点后树形DP。为什么边双缩点后会......
  • NOIP2022 总结
    一定要先把可能写出的正解写好了再去打别的暴力(时间不足导致T3建造军营推出式子但没时间写\(100\to0\))。特殊的多测不清空(T1种花未清空读入时的字符串数组\(100\t......
  • NOIP2022 游记
    说实话,\(\text{CSP-S}\)和\(\text{NOIP}\)都不怎么想写游记,答案是没感觉啥就考过来了,很疑惑进场打配置,发现键盘极其难受,摁几下摁不出来,工作人员表示只换机子不换键盘,......
  • CSP-J2022 & NOIP2022联合游寄
    CSP-J2022&NOIP2022联合游寄1.CSP-J2022Day-1感冒,喉咙疼,扁桃体发炎:(Day1(比赛日)头晕,喉咙疼,早饭吃了稍微好了一点。坐车到考点门口,发现有个人在努力地背SPFA,结果没......
  • NOIP2022 T1 种花 题解
    Part1吐槽&退役寄考场上唯一AC的题目,T3看错题以为可以删去不止1条边,T4输出没换行,寄。最后喜提100+0+0+0,给我的OI生涯画上了一个不完美的句号。Part2题解题目让统计......
  • P8865 [NOIP2022] 种花
    P8865NOIP2022种花-洛谷|计算机科学教育新生态(luogu.com.cn)。记\(a(x,y)\)代表原文的\(a_{x,y}\)。考虑对每个点统计:左上角在该点的\(\texttt{C-}\),\(\te......
  • P8866 [NOIP2022] 喵了个喵
    P8866NOIP2022喵了个喵-洛谷|计算机科学教育新生态(luogu.com.cn)。本题解中我们将图案为\(x\)的卡牌看做数字\(x\),将本题对于卡牌的操作看做对数字的操作。观......
  • NOIP2022游寄
    真的是游了,寄了Day0预感要爆炸,背了背模板(虽然没用上)Day1感觉不怎么好,买了瓶咖啡,到考场的时候发现掉了,555提前进入考场静坐密码是biu#2019miss和???(记不到了)提前......
  • NOIP2022T3
    NOIP2022T3建造军营题解[NOIP2022]建造军营题目描述A国与B国正在激烈交战中,A国打算在自己的国土上建造一些军营。A国的国土由\(n\)座城市组成,\(m\)条双向道路......
  • NOIP2022 题解
    A.种花有的人把名字写进题面,想“不朽”。签到题。枚举c和f的最左边那一列的位置,然后做一个类似前缀和的东西。B.喵了个喵压轴题。首先\(k=2n-2\)有一个非常好......