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

NOIP2022 T3 建造军营

时间:2022-12-06 19:23:31浏览次数:85  
标签:连通 int T3 fa MAXN low 军营 NOIP2022

只有 B 国炸毁了图的割边,才会使得图不连通,进而可能会导致军营不连通。也就是说,A 国可以随意地看守或不看守不是割边的边。因此想到边双缩点后树形 DP。

为什么边双缩点后会形成一棵树呢?题目保证了给定的图连通,那么缩点后的图也必然连通,而如果有多个“双连通分量”构成了环,那就不符合双连通分量的定义了,即这些首尾相连构成环的“双连通分量”应该被划在同一个双连通分量中。因此,缩点后形成的图连通且无环,那不正是一棵树吗?

已经缩了点,再思考:究竟要在缩点后形成的树上求什么?

令 \(V_u\) 表示双连通分量 \(u\) 中的点数,\(E_u\) 表示双连通分量 \(u\) 中的边数,若有 \(n\) 个双连通分量,则问题转化为:

给定一棵由 \(n\) 个点构成的无根树,每个结点有 \(2^{V_u + E_u - 1}\) 种建造军营的方案和 \(2^{V_u}\) 种不建造军营的方案。求共有多少种建造军营的方案(不能不建)。

这里假定 \(1\) 号结点为树根。

令 \(f(u, 0/1)\) 表示以 \(u\) 为根的子树中没有/有军营的方案数。

发现每种状态所涵盖的情况过多,根本不好转移。

这时,有两种思路:

  • 增加状态数量。
  • 对状态增添限制。

我选择的是后者。

令 \(f(u, 0/1)\) 表示以 \(u\) 为根的子树中没有/有军营的方案数,若有军营,则所有的军营必须与 \(u\) 连通。

在想转移之前,为了防止做无用功,最好先想想该如何统计答案。

对于每个结点 \(u\),我们强制 \(u\) 子树外的所有点都不建军营,同时强制不选 \(fa_u \to u\) 的边,再累加方案数,即可保证不重不漏。

令 \(s(u)\) 表示以 \(u\) 为根节点的子树内边数,即 \(s(u) = E_u + \sum\limits_{v \in son(u)} (s(v) + 1)\),则有 \(ans \leftarrow f(u, 1) \times 2^{s(1) - s(u) - 1}\)。

特殊地,对于 \(1\) 号结点,不存在 \(fa_1 \to 1\) 的边,此时 \(ans \leftarrow f(1, 1)\)。

明确的答案如何统计,接下来考虑转移:

显然地,\(f(u, 1) = 2^{E_u} \times \prod\limits_{v \in son(u)} 2f(v, 0)\),难点在 \(f(u, 1)\) 的转移上。

考虑每新增一个子节点 \(v\) 对 \(f(u, 1)\) 产生的贡献。

若到新增前都还未建造一个军营,则以 \(v\) 为根的子树中必须有军营,即 \(f(u, 1) \leftarrow f(u, 0) \times f(v, 1)\)。

若到新增前已经建造过军营,则以 \(v\) 为根的子树中有没有军营皆可,且当以 \(v\) 为根的子树中没有军营时,\(v\) 点是否与 \(u\) 点连通皆可,即 \(f(u, 1) \leftarrow f(u, 1) \times (2f(v, 0) + f(v, 1))\)。

综上,\(f(u, 1) \leftarrow f(u, 0) \times f(v, 1) + f(u, 1) \times (2f(v, 0) + f(v, 1))\)。

初始时,\(f(u, 0) = 2^{E_u}\),\(f(u, 1) = 2^{V_u + E_u} - f(u, 0)\)。

代码:

#include <bits/stdc++.h>

#define MAXN 500100
#define MAXM 1000100

using namespace std;

typedef long long ll;

const int MOD = 1e9 + 7;

int n, m, p;
int tot, tot2, head[MAXN], head2[MAXN];
int cnt, top, stk[MAXN], dfn[MAXN], low[MAXN], bel[MAXN];
int deg[MAXN], V[MAXN], E[MAXN], s[MAXN];
bool ins[MAXN];
ll ans, f[MAXN][2];

struct Edge {
    int to, nxt;
} e[MAXM << 1], e2[MAXM << 1];

template<typename _T>
void read(_T &_x) {
    _x = 0;
    _T _f = 1;
    char _ch = getchar();
    while (_ch < '0' || '9' < _ch) {
        if (_ch == '-') _f = -1;
        _ch = getchar();
    }
    while ('0' <= _ch && _ch <= '9') {
        _x = (_x << 3) + (_x << 1) + (_ch & 15);
        _ch = getchar();
    }
    _x *= _f;
}

template<typename _T>
void write(_T _x) {
    if (_x < 0) {
        putchar('-');
        _x = -_x;
    }
    if (_x > 9) write(_x / 10);
    putchar('0' + _x % 10);
}

void add(int u, int v) {
    e[++tot] = Edge{v, head[u]};
    head[u] = tot;
}

void add2(int u, int v) {
    e2[++tot2] = Edge{v, head2[u]};
    head2[u] = tot2;
}

void tarjan(int u, int fa) {
    dfn[u] = low[u] = ++cnt;
    ins[u] = 1, stk[++top] = u;
    for (int i = head[u], v; i; i = e[i].nxt) {
        v = e[i].to;
        if (v == fa) continue;
        if (!dfn[v]) {
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
        } else if (ins[v]) low[u] = min(low[u], dfn[v]);
    }
    if (dfn[u] == low[u]) {
        p++;
        int x;
        do {
            // 因为不需要知道每个边双连通分量里都有哪些点,只记录每个点属于哪个边双连通分量即可。
            x = stk[top--];
            ins[x] = 0;
            bel[x] = p;
            V[p]++; // 累加该边双连通分量内点数
        } while (x != u);
    }
}

ll qp(ll base, int e) { // 快速幂
    ll res = 1;
    while (e) {
        if (e & 1) res = res * base % MOD;
        base = base * base % MOD;
        e >>= 1;
    }
    return res;
}

void dfs(int u, int fa) { // dfs 计算 s[]
    s[u] = E[u];
    for (int i = head2[u], v; i; i = e2[i].nxt) {
        v = e2[i].to;
        if (v == fa) continue;
        dfs(v, u);
        s[u] += s[v] + 1;
    }
}


void dp(int u, int fa) { // 树形 DP
    for (int i = head2[u], v; i; i = e2[i].nxt) {
        v = e2[i].to;
        if (v == fa) continue;
        dp(v, u);
        // 状态转移
        f[u][1] = (f[u][1] * (((f[v][0] << 1) + f[v][1]) % MOD) % MOD + f[u][0] * f[v][1] % MOD) % MOD;
        f[u][0] = f[u][0] * ((f[v][0] << 1) % MOD) % MOD;
    }
    // 统计答案
    if (u == 1) ans = (ans + f[u][1]) % MOD; // 特判 1 号结点的特殊情况
    else ans = (ans + f[u][1] * qp(2, s[1] - s[u] - 1)) % MOD;
}

int main() {
    read(n), read(m);
    while (m--) {
        int u, v;
        read(u), read(v);
        add(u, v), add(v, u);
    }
    tarjan(1, 0); // 边双缩点
    for (int u = 1; u <= n; u++) {
        for (int i = head[u], v; i; i = e[i].nxt) {
            v = e[i].to;
            if (bel[u] != bel[v]) add2(bel[u], bel[v]); // 如果属于两个不同的边双连通分量,则将这两个边双连通分量连边
            else E[bel[u]]++; // 否则该双连通分量内边数 + 1
        }
    }
    for (int i = 1; i <= p; i++) {
        E[i] >>= 1; // 因为是无向边,每一条边会累加 2 次,故 E[i] 需要除以 2
        // 赋初值
        f[i][0] = qp(2, E[i]);
        f[i][1] = qp(2, V[i] + E[i]) - f[i][0];
    }
    dfs(1, 0);
    dp(1, 0);
    write(ans);
    return 0;
}

标签:连通,int,T3,fa,MAXN,low,军营,NOIP2022
From: https://www.cnblogs.com/chy12321/p/16960252.html

相关文章

  • NOIP2022 总结
    一定要先把可能写出的正解写好了再去打别的暴力(时间不足导致T3建造军营推出式子但没时间写\(100\to0\))。特殊的多测不清空(T1种花未清空读入时的字符串数组\(100\t......
  • 对graalvm、springboot3.0一些新特性的探究
    环境:系统:IntelcoreMacVentura13.0.1工具: Idea:2022.2.3 gradle:7.4(idea自带的)  openjdk:version"17.0.5"2022-10-18 graalvm: CE22.3.0 ......
  • NOIP2022 游记
    说实话,\(\text{CSP-S}\)和\(\text{NOIP}\)都不怎么想写游记,答案是没感觉啥就考过来了,很疑惑进场打配置,发现键盘极其难受,摁几下摁不出来,工作人员表示只换机子不换键盘,......
  • CSP-J2022 & NOIP2022联合游寄
    CSP-J2022&NOIP2022联合游寄1.CSP-J2022Day-1感冒,喉咙疼,扁桃体发炎:(Day1(比赛日)头晕,喉咙疼,早饭吃了稍微好了一点。坐车到考点门口,发现有个人在努力地背SPFA,结果没......
  • SpringBoot3.x中spring.factories功能被移除的解决方案
    背景笔者所在项目组在搭建一个全新项目的时候选用了SpringBoot3.x,项目中应用了很多SpringBoot2.x时代相关的第三方组件例如baomidou出品的mybatis-plus、dynamic-datasour......
  • Spring Boot3.0升级,踩坑之旅,附解决方案
    本文基于newbeemall项目升级SpringBoot3.0踩坑总结而来,附带更新说明:Spring-Boot-3.0-发布说明Spring-Boot-3.0.0-M5-发布说明一.编译报错,importjavax.servlet.*;......
  • 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\),将本题对于卡牌的操作看做对数字的操作。观......
  • 基于spring-boot3体验graal-vm打包本地镜像
    安装与操作系统相匹配的graal-vm(建议最新版本),并将graal-vm设为系统默认java运行环境。创建一个spring-boot3项目,项目sdk版本设为graal-vm,只引入web包,pom.xml如下(尤其需......