• 题目传送门

  • 容易想到一种暴力 DP:先转化成对于每个 \(k\) 求出 \(\max_{i\in S}|i-w_i|\le k\) 的方案数,最后差分

  • 然后问题转化成每个叶子的权值有个取值区间,注意这时我们可以把每个点的权值分成 \(<W,=W,>W\)(看作 \(0,1,2\) )三类处理,然后 DP \(f[u][0/1/2]\)

  • 然后你会很快发现这么做不行,因为选取某些叶子集合时,根节点的权值可以小于 \(W\) 也可以大于 \(W\) ,直接用 \(2^m-1\) 减掉 \(f[u][0]+f[u][2]\) 无法得到正确结果

  • 于是我们尝试考虑怎样才能在 \(<W\) 和 \(>W\) 这两个条件中去掉一个

  • 考虑权值为 \(W\) 的叶子到根的一条链

  • 容易发现根节点的权值会被改变,当且仅当这条链上存在一个点会被改变

  • 设这条链上有一个深度为奇数的点 \(u\) (偶数同理),考虑把链上所有的边都删掉之后 \(u\) 所在的连通块

  • 易得在原树上 \(u\) 的权值会被改,当且仅当删边之后就 \(u\) 所在的连通块,能让 \(u\) 的权值大于 \(W\)

  • 特殊地,如果 \(u\) 是叶子那么 \(u\) 的权值会被改当且仅当 \(u\) 在选定集合内且 \(k>0\)

  • 这样我们就实现了在 \(<W\) 和 \(>W\) 这两个条件中去掉一个

  • 我们有了一个 DP:\(f[u]\) 表示 \(u\) 的子树内的叶子节点有多少个子集能让 \(u\) 的权值大于 \(W\)

  • 如果 \(u\) 的深度为奇数(\(cnt_u\) 为 \(u\) 的子树内叶子个数):

  • \[f[u]=2^{cnt_u}-\prod_{v\in son[u]}(2^{cnt_v}-f[v])
    \]

  • 否则:

  • \[f[u]=\prod_{v\in son[u]}f[v]
    \]

  • 把所有连通块的 DP 结果用 \(2^{cnt}\) 减掉后乘起来,即为根节点权值不会被改变的方案数

  • 对每个 \(k\) 进行 DP 的复杂度为 \(O(n^2)\)

  • 考虑如何优化。我们注意到这个 DP 的转移和 \(k\) 无关,只有初始值(叶子)和 \(k\) 有关

  • 而如果一个点 \(u\) 所在的连通块根在原树中的深度为奇数,那么点 \(u\) 的初值应该为:

  • \[f[u]=[u>W]+[u+k>W]
    \]

  • 注意到当 \(k\) 不断加一的时候, \([u+k>W]\) 只会改变一次

  • 于是从小到大枚举 \(k\) 后动态 DP 即可

  • 注意由于 \(f[u]\) 可以为 \(0\) ,所以往上更新 DP 值时不能把 \(f[fa[u]]\) 直接除以 \(f[u]\) ,而需要对每个点维护一个变量表示子节点的 DP 值中有多少个 \(0\) ,另一个变量表示子节点不为 \(0\) 的 DP 值之积

  • \(O(n\log^2n)\)

Code

#include <bits/stdc++.h>
#define p2 p << 1
#define p3 p << 1 | 1 template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
} template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;} template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;} const int N = 2e5 + 5, M = N << 1, L = M << 1, rqy = 998244353; int n, l, r, dep[N], ecnt, nxt[M], adj[N], go[M], val[N], ans[N], f[N], cnt[N],
fa[N], sze[N], son[N], top[N], pos[N], idx[N], bot[N], QAQ, pw[N], re[N], c0[N],
rea = 1, cnt0;
bool lea[N], bel[N]; int qpow(int a, int b)
{
int res = 1;
while (b)
{
if (b & 1) res = 1ll * res * a % rqy;
a = 1ll * a * a % rqy;
b >>= 1;
}
return res;
} struct modi
{
int a, b; friend inline modi operator * (modi x, modi y)
{
return (modi) {(int) (1ll * x.a * y.a % rqy),
(int) ((1ll * x.a * y.b + x.b) % rqy)};
} friend inline int operator * (modi x, int y)
{
return (1ll * x.a * y + x.b) % rqy;
}
} g[N], gp[L]; void add_edge(int u, int v)
{
nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
} void dfs(int u, int fu)
{
dep[u] = dep[fu] + 1;
val[u] = dep[u] & 1 ? 1 : n;
bool is = 1;
for (int e = adj[u], v; e; e = nxt[e])
if ((v = go[e]) != fu)
{
dfs(v, u); is = 0;
if (dep[u] & 1) val[u] = Max(val[u], val[v]);
else val[u] = Min(val[u], val[v]);
}
if (is) lea[val[u] = u] = 1, ans[0] = (ans[0] + ans[0]) % rqy;
} void dfs1(int u, int fu, bool op)
{
fa[u] = fu; sze[u] = 1; bel[u] = op; cnt[u] = lea[u];
for (int e = adj[u], v; e; e = nxt[e])
if ((v = go[e]) != fu && val[v] != val[1])
{
dfs1(v, u, op);
sze[u] += sze[v]; cnt[u] += cnt[v];
if (sze[v] > sze[son[u]]) son[u] = v;
}
f[u] = (op ? val[u] > val[1] : val[u] < val[1]) ? pw[cnt[u]] : 0;
g[u].a = re[u] = 1;
if ((dep[u] & 1) ^ op)
{
for (int e = adj[u], v; e; e = nxt[e])
if ((v = go[e]) != fu && val[v] != val[1] && v != son[u])
{
g[u].a = 1ll * g[u].a * f[v] % rqy;
if (f[v]) re[u] = 1ll * re[u] * f[v] % rqy; else c0[u]++;
}
}
else
{
for (int e = adj[u], v; e; e = nxt[e])
if ((v = go[e]) != fu && val[v] != val[1] && v != son[u])
{
int tmp = (pw[cnt[v]] - f[v] + rqy) % rqy;
g[u].a = 1ll * g[u].a * tmp % rqy;
if (tmp) re[u] = 1ll * re[u] * tmp % rqy; else c0[u]++;
}
g[u].b = (pw[cnt[u]] - 1ll * g[u].a * pw[cnt[son[u]]] % rqy + rqy) % rqy;
}
bot[u] = son[u] ? bot[son[u]] : u;
} void dfs2(int u, int fu)
{
if (son[u])
{
top[son[u]] = top[u];
idx[pos[son[u]] = ++QAQ] = son[u];
dfs2(son[u], u);
}
for (int e = adj[u], v; e; e = nxt[e])
if ((v = go[e]) != fu && v != son[u] && val[v] != val[1])
top[v] = v, idx[pos[v] = ++QAQ] = v, dfs2(v, u);
} void build(int l, int r, int p)
{
if (l == r) return (void) (gp[p] = g[idx[l]]);
int mid = l + r >> 1;
build(l, mid, p2); build(mid + 1, r, p3);
gp[p] = gp[p2] * gp[p3];
} void change(int l, int r, int pos, modi v, int p)
{
if (l == r) return (void) (gp[p] = v);
int mid = l + r >> 1;
if (pos <= mid) change(l, mid, pos, v, p2);
else change(mid + 1, r, pos, v, p3);
gp[p] = gp[p2] * gp[p3];
} modi ask(int l, int r, int s, int e, int p)
{
if (e < l || s > r) return (modi) {1, 0};
if (s <= l && r <= e) return gp[p];
int mid = l + r >> 1;
return ask(l, mid, s, e, p2) * ask(mid + 1, r, s, e, p3);
} void modify(int u, int x)
{
bool fi = 1; int of;
while (1)
{
int v = top[u], w = fa[v], rf = f[v], nf;
if (fi) fi = 0, f[u] = x; nf = f[v];
if (v != bot[u]) nf = f[v] =
ask(1, n, pos[v], pos[bot[u]] - 1, 1) * f[bot[u]] % rqy;
if (!w) {of = rf; u = v; break;}
if ((dep[v] & 1) ^ bel[v]) rf = (pw[cnt[v]] - rf + rqy) % rqy,
nf = (pw[cnt[v]] - nf + rqy) % rqy;
if (!rf) c0[w]--; else re[w] = 1ll * re[w] * qpow(rf, rqy - 2) % rqy;
if (!nf) c0[w]++; else re[w] = 1ll * re[w] * nf % rqy;
g[w].a = c0[w] ? 0 : re[w];
g[w].b = (dep[w] & 1) ^ bel[w] ? 0 : (pw[cnt[w]] -
1ll * g[w].a * pw[cnt[son[w]]] % rqy + rqy) % rqy;
change(1, n, pos[w], g[w], 1);
u = w;
}
if (of == pw[cnt[u]]) cnt0--; else rea = 1ll * rea *
qpow(pw[cnt[u]] - of + rqy, rqy - 2) % rqy;
if (f[u] == pw[cnt[u]]) cnt0++;
else rea = 1ll * rea * (pw[cnt[u]] - f[u] + rqy) % rqy;
} int main()
{
int x, y;
read(n); read(l); read(r);
for (int i = 1; i < n; i++)
read(x), read(y), add_edge(x, y);
ans[0] = pw[0] = ans[n] = 1; dfs(1, 0);
for (int i = 1; i <= n; i++) pw[i] = (pw[i - 1] + pw[i - 1]) % rqy;
rea = 499122177ll * ans[0] % rqy;
for (int u = 1; u <= n; u++)
{
if (val[u] != val[1] || u == val[1]) continue;
dfs1(u, 0, dep[u] & 1);
if (!cnt[u] || lea[u]) continue;
idx[pos[u] = ++QAQ] = top[u] = u;
dfs2(u, 0);
}
build(1, n, 1);
for (int i = 1; i < n; i++)
{
if (i > 1 && val[1] >= i && lea[val[1] - i + 1] && bel[val[1] - i + 1])
modify(val[1] - i + 1, 1);
if (i > 1 && val[1] <= n - i + 1 && lea[val[1] + i - 1]
&& !bel[val[1] + i - 1]) modify(val[1] + i - 1, 1);
ans[i] = cnt0 ? 0 : rea;
}
for (int i = l; i <= r; i++) printf("%d ", (ans[i - 1] - ans[i] + rqy) % rqy);
return puts(""), 0;
}

[LOJ#3044][动态DP]「ZJOI2019」Minimax 搜索的更多相关文章

  1. Loj #3044. 「ZJOI2019」Minimax 搜索

    Loj #3044. 「ZJOI2019」Minimax 搜索 题目描述 九条可怜是一个喜欢玩游戏的女孩子.为了增强自己的游戏水平,她想要用理论的武器武装自己.这道题和著名的 Minimax 搜索有关 ...

  2. 【LOJ】#3044. 「ZJOI2019」Minimax 搜索

    LOJ#3044. 「ZJOI2019」Minimax 搜索 一个菜鸡的50pts暴力 设\(dp[u][j]\)表示\(u\)用\(j\)次操作能使得\(u\)的大小改变的方案数 设每个点的初始答案 ...

  3. LOJ3044. 「ZJOI2019」Minimax 搜索

    LOJ3044. 「ZJOI2019」Minimax 搜索 https://loj.ac/problem/3044 分析: 假设\(w(1)=W\),那么使得这个值变化只会有两三种可能,比\(W\)小 ...

  4. 「ZJOI2019」Minmax搜索

    传送门 Solution 叶子节点的变化区间是连续的,可得知非叶子节点的权值变化区间也是连续的 由此可知,\(W\)的变化值的可行域也是连续的,所以只需要看它能否变为\(W+1\)或\(W-1\) 对 ...

  5. 「ZJOI2019」&「十二省联考 2019」题解索引

    「ZJOI2019」&「十二省联考 2019」题解索引 「ZJOI2019」 「ZJOI2019」线段树 「ZJOI2019」Minimax 搜索 「十二省联考 2019」 「十二省联考 20 ...

  6. Loj #3042. 「ZJOI2019」麻将

    Loj #3042. 「ZJOI2019」麻将 题目描述 九条可怜是一个热爱打麻将的女孩子.因此她出了一道和麻将相关的题目,希望这题不会让你对麻将的热爱消失殆尽. 今天,可怜想要打麻将,但是她的朋友们 ...

  7. 【LOJ】#3042. 「ZJOI2019」麻将

    LOJ#3042. 「ZJOI2019」麻将 如何判定一个集合牌有没有胡的子集是不是胡的 就用一个\(dp[j][k][0/1]\)表示有j个连续两个的串,有k个连续1个串,有没有对子,再记一下这个集 ...

  8. loj#2537. 「PKUWC2018」Minimax

    题目链接 loj#2537. 「PKUWC2018」Minimax 题解 设\(f_{u,i}\)表示选取i的概率,l为u的左子节点,r为u的子节点 $f_{u,i} = f_{l,i}(p \sum ...

  9. Loj #3045. 「ZJOI2019」开关

    Loj #3045. 「ZJOI2019」开关 题目描述 九条可怜是一个贪玩的女孩子. 这天,她和她的好朋友法海哥哥去玩密室逃脱.在他们面前的是 \(n\) 个开关,开始每个开关都是关闭的状态.要通过 ...

随机推荐

  1. H3C根桥的选举

  2. dotnet core 使用 PowerShell 脚本

    本文告诉大家如何在 dotnet core 通过 Host PowerShell 的方法使用 PowerShell 脚本 本文提供的方法需要在 dotnet core 2.1 和以上的版本,对于 do ...

  3. Android5_浅谈Java的package机制

    当代码量越来越大,类越来越多.尤其会增加同名类的风险.所以对类进行管理就显得非常重要. 包(package)机制是java中管理类的重要手段. 包名的命名方式:业内默认的做法是使用公司的网络域名的倒写 ...

  4. apk混淆打包和反编译(转)

    前面有人写过了,我就直接引用了,大家就不乱找了.以后有问题再继续更新. 一.混淆打包.编译 1.Android 代码混淆.http://blog.csdn.net/zjclugger/article/ ...

  5. springboot多租户设计

    1. 概述 根据不同用户的请求,选择不同的数据源,不同的数据源可以是Oracle.MySQL或者其它.用到的技术栈,没有什么复杂的技术,可以看到,依赖也就加了几个而已,如下: 2. 先睹为快 如下图, ...

  6. mysql中information_schema.columns字段说明

    1. 获取所有列信息(COLUMNS) SELECT  *  FROM information_schema.COLUMNS WHERE  TABLE_SCHEMA='数据库名';  COLUMNS表 ...

  7. 使用 Postman 测试你的 API

    使用 Postman 测试你的 API Intro 最近想对 API 做一些自动化测试,看了几个工具,最后选择了 postman,感觉 postman 的设计更好一些,我们可以在请求发送之前和请求获取 ...

  8. MYSQL基本常用函数

    MYSQL基本常用函数 一.字符的操作函数 (ps:mysql中的索引都是从1开始的.) 1.instr(param1,param2) 返回子串第一次出现的索引,若找不到则返回0. param1填写操 ...

  9. 【学习】调用iframe中的方法

    当页面中有iframe时,想在主页面调用iframe中的方法,可以用contentWindow属性.但具体用时还有一点要注意,就是必须等页面加载完成才可以,否则会报错找不到函数. 例: 父页面: &l ...

  10. $Poj3179\ Corral\ the\ Cows$ 二分+离散化+二维前缀和

    Poj $Description$ 在一个二维平面上,有$N$颗草,每颗草的大小是$1*1$,左下角坐标为$x_i,y_i$.要求一个正方形,正方形的边平行于$x$或$y$轴,正方形里面包含至少$C$ ...