Loj #3044. 「ZJOI2019」Minimax 搜索

题目描述

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

可怜有一棵有根树,根节点编号为 \(1\)。定义根节点的深度为 \(1\),其他节点的深度为它的父亲的深度加一。同时在叶子节点权值给定的情况下,可怜用如下方式定义了每一个非节点的权值:

- 对于深度为奇数的非叶子节点,它的权值是它所有子节点的权值最大值。

- 对于深度为偶数的非叶子节点,它的权值是它所有子节点的权值最小值。

最开始,可怜令编号为 \(i\) 的叶子节点权值为 \(i\),并计算得到了根节点的权值为 \(W\)。

现在,邪恶的 Cedyks 想要通过修改某些叶子节点的权值,来让根节点的权值发生改变。Cedyks 设计了一个量子攻击器,在攻击器发动后,Cedyks 会随机获得一个非空的叶子节点集合 \(S\) 的控制权,并可以花费一定的能量来修改 \(S\) 中的叶子节点的权值。

然而,修改叶子节点的权值也要消耗能量,对于 \(S\) 中的叶子节点 \(i\),它的初始权值为 \(i\),假设 Cedyks 把它的权值修改成了 \(w_i\)(\(w_i\) 可以是任意整数,包括负数),则 Cedyks 在这次攻击中,需要花费的能量为 \(\max_{i\in S} |i − w_i|\)。

Cedyks 想要尽可能节约能量,于是他总是会*以最少的能量来完成攻击*,即在花费的能量最小的情况下,让根节点的权值发生改变。令 \(w(S)\) 为 Cedyks 在获得了集合 \(S\) 的控制权后,会花费的能量。特殊地,对于某些集合 \(S\),可能无论如何改变 \(S\) 中叶子节点的权值,根节点的权值都不会发生改变,这时,\(w(S)\) 的值被定义为 \(n\)。为了方便,我们称 \(w(S)\) 为 \(S\) 的稳定度。

当有 \(m\) 个叶子节点的时候,一共有 \(2^m − 1\) 种不同的叶子节点的非空集合。在发动攻击前,Cedyks 想要先预估一下自己需要花费的能量。于是他给出了一个区间 \([L, R]\),他想要知道对于每一个 \(k \in [L, R]\),有多少个集合 \(S\) 满足 \(w(S) = k\)。

输入格式

第一行输入三个整数 \(n, L, R(n \ge 2, 1 \le L \le R \le n)\)。

接下来 \(n − 1\) 行每行两个整数 \(u, v\),表示树上的一条边。

输出格式

输出一行 \(R − L + 1\) 个整数,第 \(i\) 个整数表示 \(w(S)\) 为 \(L + i − 1\) 的集合 \(S\) 有多少个。答案可能会很大,请对 \(998244353\) 取模后输出。

数据范围与提示

对于 \(100\%\) 的数据,保证 \(2\leq n \leq 10^5, 1 \le L \le R \le n\)。

\(\\\)

首先考虑\(70\)分的暴力:

考虑先求答案的前缀和再差分,即求稳定度\(\leq k\)的集合数量,记为\(ans_k\)。设\(1\)号点最终的权值是\(W\)。那么其他的点要改变的话要么为\(W+1\),要么为\(W-1\)。设\(leaf_i\)表示第\(i\)个点为根的子树中叶子节点的个数。

我们发现\(ans_1=2^{leaf_1-1}\),也就是该集合必须包含\(W\),其他的点任意。然后对于\(k>1\),我们求答案的反面,也就是使得最终\(1\)号点的权值不变的集合数量。我们先将\(1\)到\(W\)这条链上单独提出来,然后对于一个深度为奇数的点,我们要令它的所有不在这条链上的叶子节点的权值都\(\leq W\),反正则必须都\(\geq W\)。这个可以用树形\(DP\)解决。

以\(\leq W\)为例。设\(f_v\)表示使得\(v\)的权值\(\leq W\)的方案数,转移的时候按度数讨论:

  1. 度数为奇数:\(f_v=\prod_{u\in son_v}f_u\)。
  2. 度数为偶数:\(f_v=2^{leaf_v}-\prod_{u\in Son_v}(2^{leaf_u}-f_u)\)

第二类情况就是先算答案的反面在求正面。

然后考虑\(v\)是叶子的情况:\(f_v=[v\leq W]+[v+k\leq W]\)。前一个是不选\(v\),后一个是选了\(v\)。

复杂度时\(O(n*(R-L+1))\)。

正解的话发现每次\(k+1\)后最多两个叶子的权值发生了变化,用动态\(DP\)维护就好了。

具体细节还是有点多,首先将\(1\)到\(W\)这条链上的其他子树进行树剖,然后\(\leq W\)和\(\geq W\)要分别维护。维护的是概率,方便转移。对于每个点,我们要维护其虚儿子的:

\[F_v=\prod_u f_u\\
G_v=\prod_{u}(1-f_u)
\]

转移矩阵的话也奇偶讨论一下。

代码:

  1. #include<bits/stdc++.h>
  2. #define ll long long
  3. #define N 200005
  4. using namespace std;
  5. inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}
  6. const ll mod=998244353;
  7. ll ksm(ll t,ll x) {
  8. ll ans=1;
  9. for(;x;x>>=1,t=t*t%mod)
  10. if(x&1) ans=ans*t%mod;
  11. return ans;
  12. }
  13. struct info {
  14. ll a,z;
  15. info() {a=1,z=0;}
  16. info(ll _a,ll _z) {a=_a,z=_z;}
  17. ll val() {return z?0:a;}
  18. };
  19. info operator *(const info &x,const info &y) {return info(x.a*y.a%mod,x.z+y.z);}
  20. info operator /(const info &x,const info &y) {return info(x.a*ksm(y.a,mod-2)%mod,x.z-y.z);}
  21. info operator *(info x,ll y) {
  22. if(y==mod) y=0;
  23. if(y) x.a=x.a*y%mod;
  24. else x.z++;
  25. return x;
  26. }
  27. info operator /(info x,ll y) {
  28. if(y==mod) y=0;
  29. if(y) x.a=x.a*ksm(y,mod-2)%mod;
  30. else x.z--;
  31. return x;
  32. }
  33. struct matrix {
  34. ll a[4];
  35. matrix() {memset(a,0,sizeof(a));}
  36. };
  37. matrix operator *(const matrix &x,const matrix &y) {
  38. matrix z;
  39. z.a[0]=(x.a[0]*y.a[0])%mod;
  40. z.a[1]=(x.a[0]*y.a[1])%mod;
  41. z.a[2]=(x.a[2]*y.a[0]+y.a[2])%mod;
  42. z.a[3]=(x.a[2]*y.a[1]+y.a[3])%mod;
  43. return z;
  44. }
  45. const ll inv2=mod+1>>1;
  46. int n,L,R;
  47. int val[N];
  48. bool leaf[N];
  49. int size[N];
  50. ll pw[N];
  51. struct road {int to,nxt;}s[N<<1];
  52. int h[N],cnt;
  53. void add(int i,int j) {s[++cnt]=(road) {j,h[i]};h[i]=cnt;}
  54. int fa[N];
  55. int dep[N];
  56. int son[N],SIZE[N];
  57. bool key[N];
  58. void dfs(int v,int fr) {
  59. int flag=0;
  60. if(dep[v]&1) val[v]=0;
  61. else val[v]=1e9;
  62. SIZE[v]=1;
  63. for(int i=h[v];i;i=s[i].nxt) {
  64. int to=s[i].to;
  65. if(to==fr) continue ;
  66. dep[to]=dep[v]+1;
  67. flag=1;
  68. dfs(to,v);
  69. SIZE[v]+=SIZE[to];
  70. if(SIZE[son[v]]<SIZE[to]) son[v]=to;
  71. fa[to]=v;
  72. size[v]+=size[to];
  73. if(dep[v]&1) val[v]=max(val[v],val[to]);
  74. else val[v]=min(val[v],val[to]);
  75. }
  76. if(!flag) {
  77. size[v]=1;
  78. leaf[v]=1;
  79. val[v]=v;
  80. }
  81. }
  82. int dfn[N],lst[N],id;
  83. int top[N],bot[N];
  84. struct ST {
  85. int flag;
  86. info F[N],G[N];
  87. struct tree {
  88. int l,r;
  89. matrix st;
  90. }tr[N<<2];
  91. void update(int v) {tr[v].st=tr[v<<1|1].st*tr[v<<1].st;}
  92. void build(int v,int l,int r) {
  93. tr[v].l=l,tr[v].r=r;
  94. if(l==r) return ;
  95. int mid=l+r>>1;
  96. build(v<<1,l,mid),build(v<<1|1,mid+1,r);
  97. }
  98. void Modify(int v,int p) {
  99. if(tr[v].l>p||tr[v].r<p) return ;
  100. if(tr[v].l==tr[v].r) {
  101. int now=lst[p];
  102. memset(tr[v].st.a,0,sizeof(tr[v].st.a));
  103. ll *a=tr[v].st.a;
  104. if((dep[now]&1)==flag) {
  105. a[0]=F[now].val();
  106. a[1]=(mod-G[now].val())%mod;
  107. a[3]=G[now].val();
  108. } else {
  109. a[0]=G[now].val();
  110. a[1]=(mod-G[now].val())%mod;
  111. a[2]=(mod+1-G[now].val())%mod;
  112. a[3]=G[now].val();
  113. }
  114. return ;
  115. }
  116. Modify(v<<1,p),Modify(v<<1|1,p);
  117. update(v);
  118. }
  119. matrix query(int v,int l,int r) {
  120. if(l<=tr[v].l&&tr[v].r<=r) return tr[v].st;
  121. int mid=tr[v].l+tr[v].r>>1;
  122. if(r<=mid) return query(v<<1,l,r);
  123. else if(l>mid) return query(v<<1|1,l,r);
  124. else return query(v<<1|1,l,r)*query(v<<1,l,r);
  125. }
  126. ll query(int v) {
  127. if(v==bot[v]) {
  128. return F[v].val();
  129. } else {
  130. ll f=F[bot[v]].val();
  131. matrix tem=query(1,dfn[v],dfn[bot[v]]-1);
  132. return (f*tem.a[0]+tem.a[2])%mod;
  133. }
  134. }
  135. void Change(int v,int f) {
  136. for(int i=top[v];!key[fa[i]];i=top[fa[i]]) {
  137. ll f=query(i);
  138. F[fa[i]]=F[fa[i]]/f;
  139. G[fa[i]]=G[fa[i]]/(mod+1-f);
  140. }
  141. F[v]=info(1,0)*f;
  142. Modify(1,dfn[v]);
  143. for(int i=top[v];!key[fa[i]];i=top[fa[i]]) {
  144. ll f=query(i);
  145. F[fa[i]]=F[fa[i]]*f;
  146. G[fa[i]]=G[fa[i]]*(mod+1-f);
  147. Modify(1,dfn[fa[i]]);
  148. }
  149. }
  150. }Mn,Mx;
  151. int Find_top(int v) {
  152. v=top[v];
  153. while(!key[fa[v]]) v=top[fa[v]];
  154. return v;
  155. }
  156. info now;
  157. void Div(int v,int tp) {
  158. dfn[v]=++id;
  159. lst[id]=v;
  160. top[v]=tp;
  161. bot[v]=v;
  162. if(son[v]) {
  163. Div(son[v],tp);
  164. bot[v]=bot[son[v]];
  165. }
  166. for(int i=h[v];i;i=s[i].nxt) {
  167. int to=s[i].to;
  168. if(to==fa[v]||to==son[v]) continue ;
  169. Div(to,to);
  170. ll fmx=Mx.query(to),fmn=Mn.query(to);
  171. Mx.F[v]=Mx.F[v]*fmx;
  172. Mx.G[v]=Mx.G[v]*(mod+1-fmx);
  173. Mn.F[v]=Mn.F[v]*fmn;
  174. Mn.G[v]=Mn.G[v]*(mod+1-fmn);
  175. }
  176. if(leaf[v]) {
  177. ll mx=((val[v]<=val[1])+(val[v]+2<=val[1]))*inv2%mod;
  178. Mx.F[v]=Mx.F[v]*mx;
  179. ll mn=((val[v]>=val[1])+(val[v]-2>=val[1]))*inv2%mod;
  180. Mn.F[v]=Mn.F[v]*mn;
  181. }
  182. Mx.Modify(1,dfn[v]);
  183. Mn.Modify(1,dfn[v]);
  184. }
  185. void dfs2(int v) {
  186. key[v]=1;
  187. for(int i=h[v];i;i=s[i].nxt) {
  188. int to=s[i].to;
  189. if(to==fa[v]) continue ;
  190. if(val[to]==val[v]) dfs2(to);
  191. else {
  192. Div(to,to);
  193. if(dep[v]&1) {
  194. now=now*Mx.query(to);
  195. } else {
  196. now=now*Mn.query(to);
  197. }
  198. }
  199. }
  200. }
  201. ll ans[N];
  202. int main() {
  203. n=Get(),L=Get(),R=Get();
  204. pw[0]=1;
  205. for(int i=1;i<=n;i++) pw[i]=pw[i-1]*2%mod;
  206. for(int i=1;i<n;i++) {
  207. int a=Get(),b=Get();
  208. add(a,b),add(b,a);
  209. }
  210. dep[1]=1;
  211. dfs(1,0);
  212. Mx.flag=1;
  213. Mx.build(1,1,n);
  214. Mn.build(1,1,n);
  215. dfs2(1);
  216. ans[1]=inv2;
  217. ans[2]=((1+mod-now.val())*inv2+inv2)%mod;
  218. ans[n]=1;
  219. for(int i=3;i<n;i++) {
  220. int x=val[1]-i+1,y=val[1]+i-1;
  221. if(x>0&&leaf[x]&&!key[x]) {
  222. int tp=Find_top(x);
  223. if(dep[tp]&1) now=now/Mn.query(tp);
  224. else now=now/Mx.query(tp);
  225. Mx.Change(x,inv2);
  226. if(dep[tp]&1) now=now*Mn.query(tp);
  227. else now=now*Mx.query(tp);
  228. }
  229. if(y<=n&&leaf[y]&&!key[y]) {
  230. int tp=Find_top(y);
  231. if(dep[tp]&1) now=now/Mn.query(tp);
  232. else now=now/Mx.query(tp);
  233. Mn.Change(y,inv2);
  234. if(dep[tp]&1) now=now*Mn.query(tp);
  235. else now=now*Mx.query(tp);
  236. }
  237. ans[i]=((1+mod-now.val())*inv2+inv2)%mod;
  238. }
  239. ll pw2=ksm(2,size[1]);
  240. for(int i=n;i>=1;i--) ans[i]=(ans[i]-ans[i-1]+mod)*pw2%mod;
  241. ans[n]=(ans[n]-1+mod)%mod;
  242. for(int i=L;i<=R;i++) cout<<ans[i]<<" ";
  243. return 0;
  244. }

Loj #3044. 「ZJOI2019」Minimax 搜索的更多相关文章

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

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

  2. LOJ3044. 「ZJOI2019」Minimax 搜索

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

  3. [LOJ#3044][动态DP]「ZJOI2019」Minimax 搜索

    题目传送门 容易想到一种暴力 DP:先转化成对于每个 \(k\) 求出 \(\max_{i\in S}|i-w_i|\le k\) 的方案数,最后差分 然后问题转化成每个叶子的权值有个取值区间,注意这 ...

  4. loj#2537. 「PKUWC2018」Minimax

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

  5. Loj #3045. 「ZJOI2019」开关

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

  6. Loj #3042. 「ZJOI2019」麻将

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

  7. 【线段树 树链剖分 差分 经典技巧】loj#3046. 「ZJOI2019」语言【未完】

    还是来致敬一下那过往吧 题目分析 先丢代码 #include<bits/stdc++.h> ; ; ; struct node { int top,son,fa,tot; }a[maxn] ...

  8. 「ZJOI2019」Minmax搜索

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

  9. @loj - 3043@「ZJOI2019」线段树

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 九条可怜是一个喜欢数据结构的女孩子,在常见的数据结构中,可怜最喜 ...

随机推荐

  1. visa

  2. C# 流介绍 (原发布 csdn 2017-09-15 23:37:52)

    1.FileStream FileStream 详细介绍参考msdn 写数据: using (FileStream fs = new FileStream("File.FileStream& ...

  3. Effective Python 编写高质量Python代码的59个有效方法

    Effective Python 编写高质量Python代码的59个有效方法

  4. Java学习——内存机制

    Java学习——内存机制 摘要:本文主要介绍了Java的内存机制. 部分内容来自以下博客: https://www.cnblogs.com/xrq730/p/4827590.html https:// ...

  5. 2019 北森java面试笔试题 (含面试题解析)

      本人5年开发经验.18年年底开始跑路找工作,在互联网寒冬下成功拿到阿里巴巴.今日头条.北森等公司offer,岗位是Java后端开发,因为发展原因最终选择去了北森,入职一年时间了,也成为了面试官,之 ...

  6. java--set,Collections,map

    set 特点: 无序, 不允许重复 没有索引 Set<String> set = new HashSet<String>(); set.add("hello" ...

  7. nodejs块级作用域

    现在让我们了解3个关键字var.let.const,的特性和使用方法. var JavaScript中,我们通常说的作用域是函数作用域,使用var声明的变量,无论是在代码的哪个地方声明的,都会提升到当 ...

  8. Django 中使用 js 操作 cookies

    session与cookie对比 Cookie: 保存在用户浏览器端的键值对 本地可以修改:如果有敏感信息,可以被看到 Session: 保存在服务器端的键值对 服务端:保存键值对{'随机字符串':{ ...

  9. 每次都能让人头大的 Shader -- 从整合说起

    之前也说过引擎能不能提供一个一般化的开发环境给使用者, 这样使用者只需要指定他要的开发环境, 就能用它最熟悉的方式去写Shader了. 从提供者的角度来看, 因为有太多的应用场景无法确定, 所以提供无 ...

  10. Rust第二次综合练习

    啊,啊,啊 原来我一直用的linux rust1.1的老版本, 很多书上的写法都不行,得调试. 今天早上,换成了win rust1.3版本, 于是,可以按书上标准的语法来弄了. 一,main.rs u ...