//https://img2018.cnblogs.com/blog/1646268/201908/1646268-20190806114008215-138720377.jpg

P8867 [NOIP2022] 建造军营

缩点

首先考虑对于一个边双内的边是可以任意看守或者不看守的,所以可以缩点,这样缩完点的图就成了一棵树。

对于缩完点后的每一个边双,我们设 \(E_{i}\) 表示边双 \(i\) 内的边的个数,\(siz_{i}\) 表示边双内点的个数。

那么只考虑一个边双的情况的话,边能任选的方案数就是 \(2^{E_i}\),剩余的就是在此边双内建造军营的方案数,也就是 \(2^{E_i + siz_i} - 2^{E_i}\)

状态

\(f_{u,1/0}\) 表示以 \(u\) 为根的子树内建造/不建造军营的方案数。

但是涵盖情况太多,考虑增加限制。

\(f_{u, 1/0}\) 表示以 \(u\) 为根的子树内有/无军营的方案数,若有军营,则军营到 \(u\) 的路径上的边都被看守。

同时强制除 \(u\) 子树外的点都不建造军营,且 \(u\to fa_{u}\) 的边强制不看守。

转移

\(s_u\) 为子树 \(u\) 内边的个数,则有 \(ans\gets f_{u, 1} \times 2^{s_1 - s_u - 1}\)

因为上面状态里说了强制除了子树内有军营以外,其他点都没有军营,所以是用 \(f_{u,1}\) 去乘其他剩余边的自由选的方案数,其中边数后面减一是 \(u\to fa_u\) 的强制不选,所以不算入。

对于 \(1\) 号节点,很特殊,往上就没有多余的边了,直接就是 \(f_{1,1}\)

然后考虑如何转移。

对于 \(f_{u, 0}\) 就是 \(f_{u, 0} = f_{u, 0} \prod_{v\in son(u)} 2\times f_{v, 0}\),这里乘 \(2\) 是因为 \(v\to u\) 的边不在 \(f_{v, 0}\) 内计算,需要单独算。

对于 \(f_{u, 1}\),如果当前 \(u\) 内子树还没有军营,那么 \(v\) 内子树一定有军营,反之 \(v\) 子树内有无军营皆可,也就是:

\[f_{u, 1} = f_{u, 0} \times f_{v, 1} + f_{u, 1} \times (2 \times f_{v, 0} + f_{v, 1}) \]

code


/*
 * @Author: Aisaka_Taiga
 * @Date: 2023-10-27 9:06:57
 * @LastEditTime: 2023-10-27 11:14:52
 * @LastEditors: Aisaka_Taiga
 * @FilePath: \Desktop\P8867.cpp
 * The heart is higher than the sky, and life is thinner than paper.
 */
#include <bits/stdc++.h>

#define int long long
#define P 1000000007
#define N 1000100

using namespace std;

inline int read()
{
    int x = 0, f = 1;
    char c = getchar();
    while(c < '0' || c > '9'){if(c == '-') f = -1; c = getchar();}
    while(c <= '9' && c >= '0') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return x * f;
}

int n, m, head[N << 1], cnt, E[N], sd[N], dfn[N], vis[N], stk[N], top, low[N], tim, scc, siz[N], ans, s[N], f[N][2];
struct node{int v, next;}e[N << 1];
vector<int> g[N];

inline void add(int u, int v){e[++ cnt] = (node){v, head[u]}; head[u] = cnt;}

inline void tarjan(int u, int fa)
{
    dfn[u] = low[u] = ++ tim;
    stk[++ top] = u;
    vis[u] = 1;
    for(int i = head[u]; i; i = e[i].next)
    {
        int v = e[i].v;
        if(v == fa) continue;
        if(!dfn[v]) tarjan(v, u), low[u] = min(low[u], low[v]);
        else if(vis[v]) low[u] = min(low[u], dfn[v]);
    }
    if(dfn[u] == low[u])
    {
        int y; scc ++;
        while(1)
        {
            y = stk[top --];
            vis[y] = 0;
            sd[y] = scc;
            siz[scc] ++;
            if(u == y) break;
        }
    }
    return ;
}

inline int ksm(int u, int v)
{
    int res = 1;
    while(v)
    {
        if(v & 1) res = (res * u) % P;
        u = (u * u) % P, v >>= 1;
    }
    return res;
}

inline void dfs(int u, int fa)
{
    s[u] = E[u];
    for(auto v : g[u])
    {
        if(v == fa) continue;
        dfs(v, u);
        s[u] += s[v] + 1;
    }
    return ;
}

inline void DP(int u, int fa)
{
    for(auto v : g[u])
    {
        if(v == fa) continue;
        DP(v, u);
        f[u][1] = ((f[u][1] * (f[v][0] * 2 % P + f[v][1]) % P) % P + (f[u][0] * f[v][1]) % P) % P;
        f[u][0] = f[u][0] * (f[v][0] * 2 % P) % P;
    }
    if(u == 1) ans = (ans + f[u][1]) % P;
    else ans = (ans + f[u][1] * ksm(2, s[1] - s[u] - 1)) % P;
    return ;
}

signed main()
{
    n = read(), m = read();
    for(int i = 1; i <= m; i ++)
    {
        int u = read(), v = read();
        add(u, v), add(v, u);
    }
    tarjan(1, 0);
    for(int u = 1; u <= n; u ++)
    {
        for(int i = head[u]; i; i = e[i].next)
        {
            int v = e[i].v;
            if(sd[u] == sd[v]) E[sd[u]] ++;
            else g[sd[u]].emplace_back(sd[v]);
        }
    }
    for(int i = 1; i <= scc; i ++)
    {
        E[i] >>= 1;
        f[i][0] = ksm(2, E[i]);
        f[i][1] = ksm(2, siz[i] + E[i]) - f[i][0];
    }
    dfs(1, 0);
    DP(1, 0);
    // for(int i = 1; i <= n; i ++)
        // cout << sd[i] << endl;
    cout << ans << endl;
    return 0;
}
posted @ 2023-10-27 11:23  北烛青澜  阅读(38)  评论(0编辑  收藏  举报