显然直接计数是不好计的,只能从 \(dp\) 这个角度来下手。

首先用最原始最直接的方法,直接在 \(dp\) 的过程中满足题目的要求。

既然问题给在一棵树上,那么必然和树脱不了关系,因此我们应该从树形 \(dp\) 的角度下手。

因为在树形 \(dp\) 的过程中我们只能考虑父子边的选择情况,那么对于一个点 \(u\),那些限制链顶在 \(u\) 子树内的限制链显然在其父亲 \(fa\) 进行转移时是考虑不到的,因此设计状态的时候必然要使得这些链已经被满足。

那么再来考虑这些链底在 \(u\) 子树内,链顶在 \(u\) 的祖先上的这些链。

不难发现只需要满足的链是链顶深度最深的这条链,因为只要满足了它其他的链也会被满足。

所以我们在 \(dp\) 的时候只关注于当前伸出去的链中链顶最深的链,于是我们的 \(dp\) 状态就呼之欲出了。

考虑令 \(dp_{i, j}\) 表示以 \(i\) 为根的子树内,还没有被满足限制的链中链顶伸出去的链的最深的链顶深度为 \(j\) 时的方案,特别地 \(j = 0\) 时表示没有伸上来的链。

那么转移的时候只要考虑 \(u \rightarrow v\) 这条父子边选 \(0 / 1\) 即可。

选 \(1\) 时,\(v\) 伸上来的所有链都会被满足,于是有转移:

\[dp_{u, i} \times \sum\limits dp_{v, j} \rightarrow dp_{u, i}
\]

选 \(0\) 时,只需要考虑当前这个最深的链顶是否来自于 \(v\) 即可。

不来自于 \(v\) :

\[dp_{u, i} \times \sum\limits_{j = 0} ^ i dp_{v, j} \rightarrow dp_{u, i}
\]

来自于 \(v\) :

\[dp_{v, i} \sum\limits_{j = 0} ^ i dp_{u, j} \rightarrow dp_{u, i}
\]

需要注意深度相同时 \(dp_{u, i} \times dp_{v, i} \rightarrow dp_{u, i}\) 被计算了两次,需要减去。

但这样依然不能通过本题,怎么办呢?

可以发现,这个 \(dp\) 的状态量就惊人地达到了 \(O(n ^ 2)\),因此我们必须要从状态这里下手。

这时候我们引入一个叫做整体 \(dp\) 的技巧,其适用范围往往是存在一个维度上初始值并不多的情况。

其做法就是将这一个需要优化的维度放到动态开点线段树上,然后可以通过主席树或线段树合并一类技巧来优化这个 \(dp\) 的流程。

那么在本题当中,我们可以将第二维放到动态开点线段树上。

那么对于第一条转移,相当于是给 \(u\) 这个线段树上每个位置乘上 \(v\) 中所有数之和,不难发现这可以直接在线段树上完成。

对于第二条转移,相当于是对于 \(u\) 所在线段树上每个非 \(0\) 的点乘上 \(v\) 所在线段树上 \(u\) 左侧所有位置之和,不难发现这个操作是可以在线段树合并的时候同时完成的,只需要递归时记录当前区间左侧所有数之和即可。

对于第三条转移,本质上与第二条转移没有区别。

因此这个转移的过程就被我们在线段树合并的同时优化掉了,复杂度 \(O(n \log n)\)。

#include <bits/stdc++.h>
using namespace std;
#define ls t[p].l
#define rs t[p].r
#define mid (l + r >> 1)
#define rep(i, l, r) for (int i = l; i <= r; ++i)
#define Next(i, u) for (int i = h[u]; i; i = e[i].next)
const int N = 500000 + 5;
const int Mod = 998244353;
struct tree { int sum, tag, l, r;} t[N * 40];
struct edge { int v, next;} e[N << 1];
int n, m, u, v, tot, cnt, h[N], rt[N], dep[N], val[N];
int read() {
char c; int x = 0, f = 1;
c = getchar();
while (c > '9' || c < '0') { if(c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
int Inc(int a, int b) { return (a += b) >= Mod ? a - Mod : a;}
int Dec(int a, int b) { return (a -= b) < 0 ? a + Mod : a;}
int Mul(int a, int b) { return 1ll * a * b % Mod;}
void add(int u, int v) {
e[++tot].v = v, e[tot].next = h[u], h[u] = tot;
e[++tot].v = u, e[tot].next = h[v], h[v] = tot;
}
void Prefix(int u, int fa) {
dep[u] = dep[fa] + 1;
Next(i, u) if(e[i].v != fa) Prefix(e[i].v, u);
}
void down(int p) {
t[ls].sum = Mul(t[ls].sum, t[p].tag), t[rs].sum = Mul(t[rs].sum, t[p].tag);
t[ls].tag = Mul(t[ls].tag, t[p].tag), t[rs].tag = Mul(t[rs].tag, t[p].tag);
t[p].tag = 1;
}
void update(int &p, int l, int r, int x, int y, int k) {
if(!p) p = ++cnt, t[p].tag = 1; t[p].sum += k;
if(l == r) return;
if(mid >= x) update(ls, l, mid, x, y, k);
if(mid < y) update(rs, mid + 1, r, x, y, k);
}
void modify(int &p, int l, int r, int x, int y) {
if(!p) p = ++cnt, t[p].tag = 1;
if(l == r) { t[p].sum = 0; return;}
down(p);
if(mid >= x) modify(ls, l, mid, x, y);
if(mid < y) modify(rs, mid + 1, r, x, y);
t[p].sum = Inc(t[ls].sum, t[rs].sum);
}
void Merge(int &p, int k, int l, int r, int S1, int S2, int S) {
if(!p || !k) {
if(!p && !k) return;
if(!p) p = p + k, t[p].sum = Mul(t[k].sum, S1), t[p].tag = Mul(t[p].tag, S1);
else t[p].sum = Mul(t[p].sum, Inc(S2, S)), t[p].tag = Mul(t[p].tag, Inc(S2, S));
return ;
}
if(l == r) {
S1 = Inc(S1, t[p].sum), S2 = Inc(S2, t[k].sum);
int tmp = Mul(t[p].sum, t[k].sum);
t[p].sum = Inc(Mul(t[k].sum, S1), Mul(t[p].sum, Inc(S, S2)));
t[p].sum = Dec(t[p].sum, tmp);
return ;
}
down(p), down(k);
Merge(rs, t[k].r, mid + 1, r, Inc(S1, t[ls].sum), Inc(S2, t[t[k].l].sum), S);
Merge(ls, t[k].l, l, mid, S1, S2, S);
t[p].sum = Inc(t[ls].sum, t[rs].sum);
}
int query(int p, int l, int r, int x, int y) {
if(!p) return 0;
if(l >= x && r <= y) return t[p].sum;
down(p);
int ans = 0;
if(mid >= x) ans = Inc(ans, query(ls, l, mid, x, y));
if(mid < y) ans = Inc(ans, query(rs, mid + 1, r, x, y));
return ans;
}
void dfs(int u, int fa) {
update(rt[u], 0, n, val[u], val[u], 1);
Next(i, u) {
int v = e[i].v; if(v == fa) continue;
dfs(v, u), Merge(rt[u], rt[v], 0, n, 0, 0, t[rt[v]].sum);
}
modify(rt[u], 0, n, dep[u], dep[u]);
}
int main() {
n = read();
rep(i, 1, n - 1) u = read(), v = read(), add(u, v);
Prefix(1, 0);
m = read();
rep(i, 1, m) u = read(), v = read(), val[v] = max(val[v], dep[u]);
dfs(1, 0);
printf("%d", t[rt[1]].sum);
return 0;
}

当 \(dp\) 状态已经超过要求后,如果存在某一维初始值比较少,整体 \(dp\) 不失一个好的选择。

[NOI2020]命运的更多相关文章

  1. P6773 [NOI2020]命运

    整体DP 很明显计算答案需要用容斥计算,如果暴力容斥的话,就是枚举哪些路径不符合条件,在这些路径的并集中的边都不能取,其他边任意取,设当前取了$i$条路径,那么对答案的贡献是$(-1)^i2^{n-1 ...

  2. DP 优化方法大杂烩 & 做题记录 I.

    标 * 的是推荐阅读的部分 / 做的题目. 1. 动态 DP(DDP)算法简介 动态动态规划. 以 P4719 为例讲一讲 ddp: 1.1. 树剖解法 如果没有修改操作,那么可以设计出 DP 方案 ...

  3. 初中的一些OI琐屑 & APIO2020 & NOI2020

    这篇文章会发布在我的博客上 https://www.cnblogs.com/dmoransky/(一个小习惯,把信息学竞赛的学习历程记录在个人博客中). 借这篇随笔回顾并简短总结一下我的初中OI(信息 ...

  4. 一个页面实例化两个ueditor编辑器,同样的出生却有不同的命运

    今天遇到一个比较怪异的问题,有一项目需要在同一个页面上展现两个ueditor编辑器,在展现时并不任何问题,但当点击了“保存”按钮时就出错了,有其中一个ueditor在asp.net中无法获取编辑器的值 ...

  5. HDU 2571 命运

    命运 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submissi ...

  6. HDUOJ----2571(命运)(简单动态规划)

    命运 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submiss ...

  7. HDU 2571 命运 动态规划

    命运 http://acm.hdu.edu.cn/showproblem.php?pid=2571 Problem Description 穿过幽谷意味着离大魔王lemon已经无限接近了!可谁能想到, ...

  8. HDU 2571 命运 (DP)

    命运 Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Submit Status Pr ...

  9. hdu2571 命运 动态规划Dp

    转载请注明出处:http://blog.csdn.net/u012860063 题目链接:pid=2571" target="_blank">http://acm. ...

随机推荐

  1. 「HAOI2016」找相同字符

    知识点: SA,线段树,广义 SAM 原题面 Loj Luogu 给定两字符串 \(S_1, S_2\),求出在两字符串中各取一个子串,使得这两个子串相同的方案数. 两方案不同当且仅当这两个子串中有一 ...

  2. Color Models (RGB, CMY, HSI)

    目录 概 定义 RGB CMY CMYK HSI 相互的转换 RGB <=> CMY CMY <=> CMYK CMY > CMYK CMYK > CMY RGB ...

  3. HAproxy开启日志记录

    1.说明 HAproxy在默认情况不会记录日志, 不仅要在haproxy.conf中配置日志输出, 还需要修改系统日志的配置文件. 2.修改haproxy.conf 在haproxy.conf文件中增 ...

  4. css 文本基础 实战 小米官方卡片案例

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. unittest_测试报告(6)

    用例执行完成后,执行结果默认是输出在屏幕上,其实我们可以把结果输出到一个文件中,形成测试报告. unittest自带的测试报告是文本形式的,如下代码: import unittest if __nam ...

  6. django 修改模型中默认字段类型

    在ADMIN页面实现一个密码框,模型中是CharField默认类型是textinput,实现方法是在admin.py中重写widgets. 来自为知笔记(Wiz)

  7. 面试官:为什么 TCP 三次握手期间,客户端和服务端的初始化序列号要求不一样?

    大家好,我是小林. 为什么 TCP 三次握手期间,客户端和服务端的初始化序列号要求不一样的呢? 接下来,我一步一步给大家讲明白,我觉得应该有不少人会有类似的问题,所以今天在肝一篇! 正文 为什么 TC ...

  8. Solon 1.6.15 发布,增加部分jdk17特性支持

    关于官网 千呼万唤始出来: https://solon.noear.org .整了一个月多了...还得不断接着整! 关于 Solon Solon 是一个轻量级应用开发框架.支持 Web.Data.Jo ...

  9. 数据库锁(mysql)

    InnoDB支持表.行(默认)级锁,而MyISAM支持表级锁 本文着中介绍InnoDB对应的锁. mysql锁主要分为以下三类: 表级锁:开销小,加锁快:不会出现死锁:锁定粒度大,发生锁冲突的概率最高 ...

  10. 校招面试之——Java容器

    最近校招季,特把自己面试中遇到的问题整理整理,以巩固自己的知识. Java中对于容器有两大类存储方式,一种是单元素存放,还有一种就是key-value这种有关联的双元素存放了.对于Java中的容器,有 ...