P5281 [ZJOI2019] Minimax搜索 题解

Description

题目

给定一颗以 \(1\) 为根的有根树,首先按照 \(\operatorname{minimax}\) 搜索的方式(即:根节点深度定为 \(1\);叶子节点的权值为节点编号,非叶子节点的权值按照“深度为奇数在儿子中取 \(\max\),深度为偶数在儿子中取 \(\min\)”的规则自下而上递归求得)找到根节点的权值 \(w\)

定义一个叶子节点的非空子集 \(S\) 的合法方案为所有“ 通过改变 \(S\) 中的点的点权使得最终的 \(\operatorname{minimax}\) 搜索结果不为 \(w\) ”方案。

而一个合法方案的代价为“对每个点的修改量取 \(\max\) 后的结果”。

定义一个叶子节点的非空子集 \(S\) 的代价即为它的所有合法方案的代价最小值

对于给定的区间中的每一个数,求有多少个集合的代价为这个数。

答案对 \(998244353\) 取模。

Solution

Sub1 50分

首先想到模拟一遍没有修改的情况,找到 \(w\) 的位置。这个按照 \(\operatorname{minimax}\) 搜索的方式 dfs 一遍即可。

拿到 \(w\) 后,我们来考虑什么样的集合 \(S\) 能够改变最终的答案。可以发现有两种:

  • 集合包含 \(w\)。由于树的节点编号唯一,此时使用 \(1\) 的代价把 \(w\) 修改掉必然可以使最终结果不为 \(w\)
  • 集合不包含 \(w\)。那么便需要保证 \(w\) 在按照 \(\operatorname{minimax}\) 搜索的方式向上跳的过程中在某一步被代替掉。我们把从 \(w\)\(1\) 的路径叫做特殊路径

第一种情况是很好解决的。我们主要关心第二种情况的统计方案。最终答案改变,当且仅当连接到特殊路径的全部子树中存在至少一个子树在那一层的选取变得比 \(w\) 优。那么就出现了两种子树,一种要求最终的根节点权值变得比 \(w\) 大,另一种要求最终的根节点权值变得比 \(w\) 小。

我们尝试先求出一颗连接到特殊路径的子树的方案数,再尝试子树之间的方案数合并。

那么不妨先考虑要求根节点权值变得比 \(w\) 大的情况,另一种情况是类似的。不难发现,我们关心的仅仅是“比 \(w\) 大”,因此考虑 dp ,设 \(dp[u][i]\) 表示在以 \(u\) 为根节点的子树中,最优情况下恰好使用 \(i\) 为代价能够使得 \(u\) 的权值比 \(w\) 大。

考虑转移:

  • 边界条件,即点 \(u\) 为叶子节点:

    1. \(u>w\),则已经达到标准,不管选不选 \(u\) 都无需耗费任何代价。因此令 \(dp[u][0]=2\)

    2. \(u\leq w\),则当前的 \(u\) 不合法,如果选 \(u\) 则需耗费 \(w+1-u\) 的代价让它变得合法,如果不选 \(u\) 则一定不合法,耗费 \(n\) 的代价。因此令 \(dp[u][w+1-u]=dp[u][n]=1\)

  • 对于非叶子节点:

    1. 若这一层要求取 \(\max\),则 \(u\) 只要有一个儿子权值大于 \(w\) 即可。我们将 \(u\) 的儿子的 dp 数组两两合并。即:

    \[dp[u][i]=dp'[u][i]\times\sum^n_{j=i+1} dp[v][j]+dp[v][i]\times\sum^n_{j=i+1} dp'[u][j]+dp'[u][i]\times dp[v][i] \]

    表示:“一个代价比 \(i\) 大一个代价为 \(i\) 的方案”与“两个代价均为 \(i\) 的方案”。

    1. 若这一层要求取 \(\max\),则要求 \(u\) 全部儿子权值大于 \(w\)。同理有:

    \[dp[u][i]=dp'[u][i]\times\sum^{i-1}_{j=0} dp[v][j]+dp[v][i]\times\sum^{i-1}_{j=0} dp'[u][j]+dp'[u][i]\times dp[v][i] \]

    表示:“一个代价比 \(i\) 小一个代价为 \(i\) 的方案”与“两个代价均为 \(i\) 的方案”。

这样就可以拿到每一颗连在特殊路径上的子树的全部有效信息了。

接下来考虑合并。不难发现“所有连在特殊路径上的子树只需要有一个满足”这一要求正相当于非叶子节点的第一种转移,即取后缀和。

至此可以做到时间复杂度 \(O(n^2)\)\(50pt\)

Sub1 50分

前置知识:线段树合并。模板

由于每个叶子节点会使所在子树最多增加 \(2\) 个 dp 数组的不为 \(0\) 的位置,对于任何一棵子树,有值的位置为 \(O(子树大小)\),那么用线段树合并维护 dp 数组即可。

这道题算是比较难的线段树合并。线段树合并的关键在于:合并的过程中还要维护当前位置前缀/后缀的一些信息并且做到“边合并边修改”。

对于这题,需要实现两种合并,分别是前缀求和相乘和后缀求和相乘。

还需要注意的是,虽然最终方案不包括空集,但是过程中的每一棵子树都应该包含空集,才能转移。最后在 \(dp[1][n]\) 减去 \(1\) 即可。

详细操作过程可以看代码。

最终时间复杂度: \(O(n log n)\)

#include<bits/stdc++.h>
#define mid (l+r>>1)
#define pb push_back
#define N 200005
#define mod 998244353
using namespace std;
int n,ql,qr,f[N],dep[N],w,sum[N<<6],tag[N<<6],ls[N<<6],rs[N<<6],tot,rt[N];
vector<int>t[N];
inline int mo(int x){return x<mod?x:x-mod;}
#define getc (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<15,stdin),cs==ct)?0:*cs++)
char cb[1<<15],*cs,*ct;
inline void read(int &num){
    char ch;while(!isdigit(ch=getc));
    for(num=ch-'0';isdigit(ch=getc);num=num*10+ch-'0');
}
inline void wq(const int& x) {
    int t=x;static char _wq_buffer[39];int bp=-1;
    if(!t)return putchar('0'),void();
    while(t)_wq_buffer[++bp]=t%10+'0',t/=10;
    for(int i=bp;i>=0;i--)putchar(_wq_buffer[i]);
}
inline int dfs(int u,int fa){
	f[u]=fa,dep[u]=dep[fa]+1;
	int res=t[u].size()==1?u:dep[u]&1?1:n;
	for(int v:t[u])if(v!=fa)res=dep[u]&1?max(res,dfs(v,u)):min(res,dfs(v,u));
	return res;
}
inline void F(int p,int k){sum[p]=1ll*sum[p]*k%mod,tag[p]=1ll*tag[p]*k%mod;}
inline void push_down(int p){
	if(tag[p]==1)return;
	if(ls[p])F(ls[p],tag[p]);
	if(rs[p])F(rs[p],tag[p]);
	tag[p]=1;
}
inline void push_up(int p){sum[p]=mo(sum[ls[p]]+sum[rs[p]]);}
inline void addsum(int &p,int l,int r,int x,int k){
	if(!p)p=++tot,tag[p]=1;
	if(l==r)return sum[p]=mo(sum[p]+k),void();
	push_down(p);
	if(x<=mid)addsum(ls[p],l,mid,x,k);
	if(x>mid)addsum(rs[p],mid+1,r,x,k);
	push_up(p);
}
inline void as(int u,int x,int k){addsum(rt[u],0,n,x,k);}
inline int mg1(int x,int y,int l,int r,int s1,int s2){//all<->left
	if(!x||!y)return (!x&&!y)?0:!x?(F(y,s1),y):(F(x,s2),x);
	if(l==r)return sum[x]=(1ll*sum[x]*s2+1ll*sum[y]*s1+1ll*sum[x]*sum[y])%mod,x;
	push_down(x),push_down(y);
	rs[x]=mg1(rs[x],rs[y],mid+1,r,mo(s1+sum[ls[x]]),mo(s2+sum[ls[y]]));
	ls[x]=mg1(ls[x],ls[y],l,mid,s1,s2);
	return push_up(x),x;
}
inline int mg2(int x,int y,int l,int r,int s1,int s2){//any<->right
	if(!x||!y)return (!x&&!y)?0:!x?(F(y,s1),y):(F(x,s2),x);
	if(l==r)return sum[x]=(1ll*sum[x]*s2+1ll*sum[y]*s1+1ll*sum[x]*sum[y])%mod,x;
	push_down(x),push_down(y);
	ls[x]=mg2(ls[x],ls[y],l,mid,mo(s1+sum[rs[x]]),mo(s2+sum[rs[y]]));
	rs[x]=mg2(rs[x],rs[y],mid+1,r,s1,s2);
	return push_up(x),x;
}
inline void out(int p,int l,int r){
	if(l==r)return wq(sum[p]-(l==n)),putchar(' '),void();
	push_down(p);
	if(ql<=mid)out(ls[p],l,mid);
	if(qr>mid)out(rs[p],mid+1,r);
}
inline void dfs2(int u,bool op){//op==1 means <w   op==0 means >w
	if(t[u].size()!=1)op^(dep[u]&1)?as(u,n,1):as(u,0,1);
	else op?(u<w?as(u,0,2):(as(u,n,1),as(u,u-w+1,1))):(u>w?as(u,0,2):(as(u,n,1),as(u,w+1-u,1)));
	for(int v:t[u])if(v!=f[u])dfs2(v,op),rt[u]=op^(dep[u]&1)?mg2(rt[u],rt[v],0,n,0,0):mg1(rt[u],rt[v],0,n,0,0);
}
int main(){
	read(n),read(ql),read(qr);
	for(int i=1,u,v;i<n;i++)read(u),read(v),t[u].pb(v),t[v].pb(u);
	w=dfs(1,0),as(0,1,1),as(0,n,1);
	for(int u=w;u!=1;u=f[u])for(int v:t[f[u]])if(v!=f[f[u]]&&v!=u)dfs2(v,dep[u]&1),rt[0]=mg2(rt[0],rt[v],0,n,0,0);
	out(rt[0],0,n);
}
posted @ 2025-06-04 12:33  linjingxiang  阅读(48)  评论(0)    收藏  举报