ARC 086 E - Smuggling Marbles(dp + 启发式合并)

时间:2021-12-17 22:52:25

题意

Sunke 有一棵 \(N + 1\) 个点的树,其中 \(0\) 为根,每个点上有 \(0\) 或 \(1\) 个石子, Sunke 会不停的进行如下操作直至整棵树没有石子 :

  • 把 \(0\) 上面的石子从树上拿走放入口袋 ;
  • 把每个点上的石子移到其父亲上 ;
  • 对于每个点 , 若其石子数 \(≥ 2\) , 则移除该点所有石子(不放入口袋)。

求对于所有 \(2^{N+1}​\) 种放置石子的方案 , 最终 Snuke 口袋中石子数是多少 , 对 \(10^9+7​\) 取模 .

\((1 \le N \le 2000) \ 400\mathrm{pts} \\ (1\le N \le 200000) \ 1000\mathrm{pts}.\)

题解

\(400\mathrm{pts}\)

我们不难发现这个操作是层层独立的... 所以我们可以考虑隔离每层来算答案

考虑一层答案对于最终的贡献 那么我们有一个显然的 \(dp\)

就是令 \(dp[u][0/1]\) 为 \(u\) 没/有 石子的方案数 (已经考虑完了 \(u\) 的子树)

我们不难发现 我们只要考虑它儿子贡献出来的方案数

我们发现有多个石子一起合并上来的方案数不好算... 所以我们就可以用所有方案数减去贡献 \(1\) 个的方案数

那么我们令 \(All\) 为所有方案数 , 就有

\[\displaystyle All=\prod_{v \in G[u]} (dp[v][0]+dp[v][1])
\]

然后我们令 \(Zero\) 为儿子全是 \(0\) 的方案数 , 就有

\[\displaystyle Zero = \prod _{v \in G[u]} dp[v][0]
\]

然后又令 \(One\) 为有一个儿子为 \(1\) 的方案数 , 就有

\[\displaystyle One = \sum_{v \in G[u]} \frac{Zero \times dp[v][1]}{dp[v][0]}
\]

那我们就可以轻易更新当前的答案了

\[dp[u][0]=All-One \\ dp[u][1]=One
\]

然后每次考虑了一层后 (一开始我们只初始化了当层的答案)

我们最后要把 \(dp[0][1]\) 乘上别的层数的方案数 也就是 \(2^{n + 1- tot[dep]}\) 然后加起来就是答案了..(代码见文末)

那么 \(400\mathrm{pts}\) 就到手了qwq


然后我们考虑一下如何优化

有一个经常使用的套路 那么就是启发式合并了...

我们把儿子 \(dp\) 状态最多继承上来 然后其他的状态暴力合并上去

把别的 \(dp\) 状态暴力合并上来就行了

为了方便转移 和 空间问题 我们每个点要动态开空间

就是我们每个点开个 vector<pair<long long, long long> >

它的下标从大到小 表示 当前点向下的深度从小到大

first 代表原来的 [0] ; second 代表原来的 [1] .

然后转移的时候下标就有些细节要注意一下

然后分析一波时间复杂度qwq

其实是 \(O(n)\) 的 , 因为两个状态只会在其 \(\mathrm{LCA}\) 上合并,然后同一层两两点的 \(\mathrm{LCA}\) 只会有该层点数 \(−1\) 个。

但我需要求一个逆元 时间复杂度就变成 $O(n \log n) $ 了.... 但还是速度还行 (267ms)

那个如果用前缀积 和 后缀积 的话就可以优化成 \(O(n)\) 了 但是不想写了...

代码

\(400\mathrm{pts}\)

#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
using namespace std; inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;} inline int read() {
int x = 0, fh = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * fh;
} void File() {
#ifdef zjp_shadow
freopen ("E.in", "r", stdin);
freopen ("E.out", "w", stdout);
#endif
} typedef long long ll;
const ll Mod = 1e9 + 7;
const int N = 2010;
ll dp[N][2], ans = 0;
ll fpm(ll x, ll power) {
ll res = 1;
for (; power; power >>= 1, (x *= x) %= Mod)
if (power & 1) (res *= x) %= Mod;
return res;
} int fa[N], n, dep[N], tot[N];
vector<int> G[N]; int main () {
File();
n = read();
For (i, 1, n) {
fa[i] = read();
dep[i] = dep[fa[i]] + 1;
G[fa[i]].push_back(i);
++ tot[dep[i]];
}
++ tot[0];
For (d, 0, n) {
Fordown(i, n, 0) {
if (dep[i] > d) continue ;
if (dep[i] == d) {
dp[i][0] = dp[i][1] = 1;
continue ;
}
ll All = 1, Zero = 1;
for (int v : G[i]) {
(All *= (dp[v][0] + dp[v][1]) % Mod) %= Mod;
(Zero *= dp[v][0]) %= Mod;
}
ll One = 0;
for (int v : G[i])
(One += Zero * fpm(dp[v][0], Mod - 2) % Mod * dp[v][1] % Mod) %= Mod;
dp[i][0] = ((All - One) % Mod + Mod) % Mod;
dp[i][1] = One;
}
(ans += dp[0][1] * fpm(2, n + 1 - tot[d]) % Mod) %= Mod;
}
printf ("%lld\n", ans);
return 0;
}

\(1000\mathrm{pts}\)

#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
using namespace std; inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;} inline int read() {
int x = 0, fh = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * fh;
} void File() {
#ifdef zjp_shadow
freopen ("E.in", "r", stdin);
freopen ("E.out", "w", stdout);
#endif
} typedef long long ll;
typedef pair<ll, ll> pll;
#define fir first
#define sec second
#define mp make_pair
const ll Mod = 1e9 + 7;
const int N = 201000; ll ans = 0;
ll fpm(ll x, ll power) {
ll res = 1;
for (; power; power >>= 1, (x *= x) %= Mod)
if (power & 1) (res *= x) %= Mod;
return res;
} int fa[N], n, tot[N];
vector<int> G[N]; int id[N], num = 0, d[N];
vector<pll> dp[N]; ll All[N], Zero[N], One[N]; void Dfs(int u) {
int son = n + 1;
for (int v : G[u]) { Dfs(v); if (d[v] > d[son]) son = v;}
if (son != n + 1) id[u] = id[son], d[u] = d[son] + 1;
else id[u] = ++num;
dp[id[u]].push_back(mp(1, 1)); if ((int)G[u].size() == 1) return ; For (i, 0, d[u] - 1)
All[i] = 1, Zero[i] = 1, One[i] = 0; int nowdep;
for (int v : G[u])
For (i, 0, d[v]) {
nowdep = (d[son] - d[v]) + i;
pll sta = dp[id[v]][i];
(All[nowdep] *= (sta.fir + sta.sec)) %= Mod;
(Zero[nowdep] *= sta.fir) %= Mod;
} for (int v : G[u])
For (i, 0, d[v]) {
nowdep = (d[son] - d[v]) + i;
pll sta = dp[id[v]][i];
(One[nowdep] += Zero[nowdep] * fpm(sta.fir, Mod - 2) % Mod * sta.sec % Mod) %= Mod;
} For (i, 0, d[u] - 1) {
dp[id[u]][i].fir = (All[i] - One[i] + Mod) % Mod;
dp[id[u]][i].sec = One[i];
}
} int dep[N]; int main () {
File();
n = read();
For (i, 1, n) {
fa[i] = read();
G[fa[i]].push_back(i);
dep[i] = dep[fa[i]] + 1;
++ tot[dep[i]];
}
++ tot[0];
d[n + 1] = -1; Dfs(0); For (i, 0, d[0]) {
pll sta = dp[id[0]][i];
(ans += sta.sec * fpm(2, n + 1 - tot[d[0] - i]) % Mod) %= Mod;
}
printf ("%lld\n", ans);
return 0;
}

ARC 086 E - Smuggling Marbles(dp + 启发式合并)的更多相关文章

  1. BZOJ4919 &lbrack;Lydsy1706月赛&rsqb;大根堆 【dp &plus; 启发式合并】

    题目链接 BZOJ4919 题解 链上的\(LIS\)维护一个数组\(f[i]\)表示长度为\(i\)的\(LIS\)最小的结尾大小 我们可以用\(multiset\)来维护这个数组,子树互不影响,启 ...

  2. AtCoder Regular Contest 086 E - Smuggling Marbles(树形迭屁)

    好强的题. 方案不好算,改成算概率,注意因为是模意义下的概率所以直接乘法逆元就好不要傻傻地开double. 设$f[i][d][0]$为第i个节点离d层的球球走到第i个点时第i个点没有球的概率, $f ...

  3. &lbrack;2016北京集训试题7&rsqb;thr-&lbrack;树形dp&plus;树链剖分&plus;启发式合并&rsqb;

    Description Solution 神仙操作orz. 首先看数据范围,显然不可能是O(n2)的.(即绝对不是枚举那么简单的),我们考虑dp. 定义f(x,k)为以x为根的子树中与x距离为k的节点 ...

  4. AtCoder AGC007E Shik and Travel &lpar;二分、DP、启发式合并&rpar;

    题目链接 https://atcoder.jp/contests/agc007/tasks/agc007_e 题解 首先有个很朴素的想法是,二分答案\(mid\)后使用可行性DP, 设\(dp[u][ ...

  5. P5979 &lbrack;PA2014&rsqb;Druzyny dp 分治 线段树 分类讨论 启发式合并

    LINK:Druzyny 这题研究了一下午 终于搞懂了. \(n^2\)的dp很容易得到. 考虑优化.又有大于的限制又有小于的限制这个非常难处理. 不过可以得到在限制人数上界的情况下能转移到的最远端点 ...

  6. Codeforces 1455G - Forbidden Value(map 启发式合并&plus;DP)

    Codeforces 题面传送门 & 洛谷题面传送门 首先这个 if 与 end 配对的结构显然形成一个树形结构,考虑把这棵树建出来,于是这个程序的结构就变为,对树进行一遍 DFS,到达某个节 ...

  7. 2018&period;10&period;14 loj&num;516&period; DP 一般看规律(启发式合并)

    传送门 注意到一种颜色改了之后就不能改回去了. 因此可以启发式合并. 每次把小的合并给大的. 这样每个数最多被合并logloglog次. 如果维护一棵比较下标的平衡树的话,对于答案有贡献的就是每个数与 ...

  8. 银河战舰 &lbrack;启发式合并&plus;dp&rsqb;

    题面 思路 我们首先考虑传统的链上LIS做法:保存每个长度的LIS末端的最小值,二分查找 那么这道题其实就只是搬到树上来做了而已 我们考虑一个节点,假设它的儿子已经处理完毕了 那么我们选择LIS最长的 ...

  9. loj516 DP一般看规律(set启发式合并)

    题目: https://loj.ac/problem/516 分析: 每次将一个颜色更改为另一个颜色相当于将两个集合合并 然后对于答案的更新,一个点插入到一个集合中,那么可能更新答案的就是其前驱节点或 ...

随机推荐

  1. Windows下查看JDK是否安装以及安装路径

    查看JDK是否已经安装,可以在cmd窗口里输入java -version,如果没有提示出错,就表示已经安装. 查看JDK的安装路径,可以输入java -verbose,会返回很多信息,其中就包含了JD ...

  2. sql数据库 管理处理问题--维护计划

    问题:SQLServer 错误: 15404,无法获取有关 Windows NT 组/用户 MYPC/Administrator' 的信息,错误代码 0x534. [SQLSTATE 42000] ( ...

  3. gson 自定义对象转换格式

    有时候我们希望gson按照我们想要的方式转换,比如将日期转换为时间戳 class GsonBuilderUtil { public static Gson create() { GsonBuilder ...

  4. 编写高质量代码改善C&num;程序的157个建议&lbrack;优先考虑泛型、避免在泛型中声明静态成员、为泛型参数设定约束&rsqb;

    前言 泛型并不是C#语言一开始就带有的特性,而是在FCL2.0之后实现的新功能.基于泛型,我们得以将类型参数化,以便更大范围地进行代码复用.同时,它减少了泛型类及泛型方法中的转型,确保了类型安全.委托 ...

  5. SQLite学习手册&lpar;内置函数&rpar;

    一.聚合函数: SQLite中支持的聚合函数在很多其他的关系型数据库中也同样支持,因此我们这里将只是给出每个聚集函数的简要说明,而不在给出更多的示例了.这里还需要进一步说明的是,对于所有聚合函数而言, ...

  6. RPM常见用法

    rpm常见的用法: 命令 说明 rpm -i <.rpm file name> 安装指定的 .rpm 文件 rpm -U <.rpm file name> 用指定的.rpm文件 ...

  7. 关于&OpenCurlyDoubleQuote;svn&colon; Can&&num;39&semi;t connect to host &&num;39&semi;&ast;&period;&ast;&period;&ast;&period;&ast;&&num;39&semi;&colon; 由于连接方在一段时间后没有正确答复或连接”的解决方法

    阿里云服务器环境(PHP+Nginx+MySQL) [原因1]svnserve.conf 没写好,当然你先备份一份先: cp svnserve.conf svnserve.conf.bak 打开此文件 ...

  8. 《Linux内核设计与实现》第4章读书整理

    第四章   进程调度 4.1多任务 无论在单处理器或者多处理机器上,多任务操作系统都能使多个进程处于堵塞或者睡眠状态. 非抢占式多任务:除非进程自己主动停止运行,否则它会一直执行. 抢占式多任务:进程 ...

  9. C&num;分解质因数

    using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace app ...

  10. ubuntu14&period;0安装ITK的步骤

    (1) sudo apt-get install cmake (2) sudo apt-get install cmake-curses-gui (3)下载安装包InsightToolkit-4.11 ...