原文链接www.cnblogs.com/zhouzhendong/p/UOJ468.html

前言

毒瘤题

题解

首先,将问题稍加转化,将“等于k”转化为“小于等于k”减去“小于k”。

然后,考虑在有一个变化量限制k时,所有的叶子会怎样变化。

我们称原本根的权值对应的节点到根的路径为“主链”,那么,只要主链的任何一个节点的权值发生变化,那么根节点权值就发生变化。

我们称一个主链上的节点的在主链上的儿子为“主儿子”。

对于主链上的一个节点,假设他深度为奇数,那么他的所有子树中,除了主儿子所在子树以外的所有叶子节点都会加上k。否则,假设他深度为偶数,那么这些叶子节点会减去k。

于是,接下来我们就可以得到一个 \(O(n)\) 的 DP 方法。即

对于深度为奇数的主链的子树, \(dp[x][0]\)、\(dp[x][1]\) 分别表示在子树x的所有叶子节点集合操作时,有 \(dp[x][1]\) 个集合可以使得 x 的权值大于 1 号点原先的权值,有 \(dp[x][0]\) 个不能。

对于深度为偶数的主链的子树, \(dp[x][1]\)、\(dp[x][0]\) 分别表示在子树x的所有叶子节点集合操作时,有 \(dp[x][1]\) 个集合可以使得 x 的权值小于 1 号点原先的权值,有 \(dp[x][1]\) 个不能。

请读者自行列出 DP 转移。

事实上,这种 DP 状态是可以简化的。由于对于一个 x,\(dp[x][0]+dp[x][1]\) 是固定的,所以我们只需要记一个。虽然这不影响得分。

至此,我们得到了一个单次 DP \(O(n)\),总时间复杂度 \(O((R-L)\cdot n)\) 的做法。

我们发现,当k的值从小到大不断变大时,只会有 \(O(n)\) 个叶子的 DP 值发生变化,每次变化会影响它到根的路径。

简单分析即可发现这里可以用动态DP来维护。

于是我们就得到了一个 \(O(n\log ^ 2 n)\) 的做法。

注意维护的时候可以会遇到乘0和除0的情况,注意特判。

代码

#pragma GCC optimize("Ofast","inline")
#include <bits/stdc++.h>
#define clr(x) memset(x,0,sizeof x)
#define For(i,a,b) for (int i=(a);i<=(b);i++)
#define Fod(i,b,a) for (int i=(b);i>=(a);i--)
#define pb(x) push_back(x)
#define mp(x,y) make_pair(x,y)
#define fi first
#define se second
#define next Next
#define outval(x) cerr<<#x" = "<<x<<endl
#define outtag(x) cerr<<"-----------------"#x"-----------------\n"
#define outarr(a,L,R) cerr<<#a"["<<L<<".."<<R<<"] = ";\
For(_x,L,R) cerr<<a[_x]<<" ";cerr<<endl;
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair <int,int> pii;
LL read(){
LL x=0,f=0;
char ch=getchar();
while (!isdigit(ch))
f=ch=='-',ch=getchar();
while (isdigit(ch))
x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return f?-x:x;
}
const int N=200005,mod=998244353;
int Pow(int x,int y){
int ans=1;
for (;y;y>>=1,x=(LL)x*x%mod)
if (y&1)
ans=(LL)ans*x%mod;
return ans;
}
void Add(int &x,int y){
if ((x+=y)>=mod)
x-=mod;
}
void Del(int &x,int y){
if ((x-=y)<0)
x+=mod;
}
int Add(int x){
return x>=mod?x-mod:x;
}
int Del(int x){
return x<0?x+mod:x;
}
int n,qL,qR;
vector <int> e[N];
int depth[N],size[N],son[N],val[N];
int fa[N],leaf[N],clf[N],pw2[N],clf2[N];
int fun(int k,int a,int b){
return k&1?max(a,b):min(a,b);
}
void dfs(int x,int pre,int d){
depth[x]=d,size[x]=1,son[x]=0,fa[x]=pre;
if (e[x].size()==1&&d>1)
val[x]=x,leaf[x]=clf[x]=1;
else
val[x]=d&1?0:n;
for (auto y : e[x])
if (y!=pre){
dfs(y,x,d+1);
clf[x]+=clf[y];
size[x]+=size[y];
val[x]=fun(d,val[x],val[y]);
if (!son[x]||size[y]>size[son[x]])
son[x]=y;
}
}
int next[N],fad[N];
void dfs2(int x,int d){
fad[x]=d;
for (auto y : e[x])
if (y!=fa[x]&&y!=next[x])
dfs2(y,d);
}
void GetNext(){
int x=val[1];
for (int y=fa[x];y;x=y,y=fa[x])
next[y]=x,dfs2(y,depth[y]);
}
int top[N],I[N],aI[N],Time=0;
void GetTop(int x,int tp){
top[x]=tp,I[x]=++Time,aI[Time]=x;
if (son[x]&&son[x]!=next[x])
GetTop(son[x],tp);
for (auto y : e[x])
if (!I[y])
GetTop(y,y);
}
int ans[N];
int dp[N];
int delta;
void DP(int x){
dp[x]=0;
if (x==val[1]){
dp[x]=1;
return;
}
if (leaf[x]){
if (fad[x]&1){
dp[x]+=(x+delta>val[1])==(depth[x]&1);
dp[x]+=(x>val[1])==(depth[x]&1);
}
else {
dp[x]+=(x-delta>=val[1])==(depth[x]&1);
dp[x]+=(x>=val[1])==(depth[x]&1);
}
return;
}
dp[x]=1;
for (auto y : e[x])
if (y!=fa[x]){
DP(y);
if (y!=next[x])
dp[x]=(LL)dp[x]*dp[y]%mod;
}
dp[x]=Del(pw2[clf2[x]]-dp[x]);
}
struct int0{
int v,c;
int0(){}
int0(int _v,int _c){
v=_v,c=_c;
}
int f(){
return c?0:v;
}
void operator *= (int x){
if (!x)
c++;
else
v=(LL)v*x%mod;
}
void operator /= (int x){
if (!x)
c--;
else
v=(LL)v*Pow(x,mod-2)%mod;
}
}v2[N],now;
int0 Get(){
int0 ans=int0(1,0);
for (int x=val[1];x;x=fa[x])
ans*=Del(pw2[clf2[x]]-dp[x]);
return ans;
}
struct fuck{
int a,b;
fuck(){}
fuck(int _a,int _b){
a=_a,b=_b;
}
friend fuck operator * (fuck x,fuck y){
return fuck((LL)x.a*y.a%mod,((LL)x.a*y.b+x.b)%mod);
}
int calc(int x){
return ((LL)a*x+b)%mod;
}
}v[N];
int mxd[N];
namespace Seg{
fuck s[N<<2];
void Build(int rt,int L,int R){
if (L==R)
return (void)(s[rt]=v[aI[L]]);
int mid=(L+R)>>1,ls=rt<<1,rs=ls|1;
Build(ls,L,mid);
Build(rs,mid+1,R);
s[rt]=s[ls]*s[rs];
}
void update(int rt,int L,int R,int x,fuck v){
if (L==R)
return (void)(s[rt]=v);
int mid=(L+R)>>1,ls=rt<<1,rs=ls|1;
if (x<=mid)
update(ls,L,mid,x,v);
else
update(rs,mid+1,R,x,v);
s[rt]=s[ls]*s[rs];
}
fuck query(int rt,int L,int R,int xL,int xR){
if (R<xL||L>xR)
return fuck(1,0);
if (xL<=L&&R<=xR)
return s[rt];
int mid=(L+R)>>1,ls=rt<<1,rs=ls|1;
return query(ls,L,mid,xL,xR)*query(rs,mid+1,R,xL,xR);
}
}
void update(int x){
while (1){
Seg::update(1,1,n,I[x],v[x]);
x=top[x];
int f=fa[x];
if (val[x]!=val[1])
v2[f]/=dp[x];
else
now/=Del(pw2[clf2[x]]-dp[x]);
dp[x]=Seg::query(1,1,n,I[x],I[mxd[x]]).calc(1);
if (val[x]!=val[1])
v2[f]*=dp[x],v[f].a=v2[f].f(),x=f;
else {
now*=Del(pw2[clf2[x]]-dp[x]);
break;
}
}
}
vector <int> upds[N];
int main(){
n=read(),qL=read(),qR=read();
pw2[0]=1;
For(i,1,n)
pw2[i]=Add(pw2[i-1]<<1);
For(i,1,n-1){
int x=read(),y=read();
e[x].pb(y),e[y].pb(x);
}
dfs(1,0,1);
GetNext();
GetTop(1,1);
For(i,1,n)
clf2[i]=clf[i];
for (int x=val[1];x;x=fa[x])
clf2[x]-=clf[next[x]];
delta=1,DP(1),now=Get(),ans[1]=Del(pw2[clf[1]]-now.f());
For(x,1,n){
if (leaf[x])
v[x]=fuck(0,dp[x]);
else {
v2[x]=int0(Del(-1),0);
for (auto y : e[x])
if (y!=fa[x]&&y!=next[x]&&y!=son[x])
v2[x]*=dp[y];
v[x]=fuck(v2[x].f(),pw2[clf2[x]]);
}
}
For(x,1,n)
if (top[x]==x)
mxd[x]=x;
For(x,1,n)
if (depth[x]>depth[mxd[top[x]]])
mxd[top[x]]=x;
Seg::Build(1,1,n);
For(i,1,n){
if (!leaf[i]||i==val[1])
continue;
if (fad[i]&1){
if (i<val[1])
upds[val[1]-i+1].pb(i);
}
else {
if (i>val[1])
upds[i-val[1]+1].pb(i);
}
}
For(i,2,n-1){
delta=i;
for (auto x : upds[i]){
int tmp=0;
if (fad[x]&1){
tmp+=(x+delta>val[1])==(depth[x]&1);
tmp+=(x>val[1])==(depth[x]&1);
}
else {
tmp+=(x-delta>=val[1])==(depth[x]&1);
tmp+=(x>=val[1])==(depth[x]&1);
}
v[x]=fuck(0,tmp);
update(x);
}
ans[i]=Del(pw2[clf[1]]-now.f());
}
ans[n]=Del(pw2[clf[1]]-1);
For(i,qL,qR)
printf("%d ",Del(ans[i]-ans[i-1]));
puts("");
return 0;
}

UOJ#468. 【ZJOI2019】Minimax搜索 动态DP的更多相关文章

  1. [ZJOI2019]Minimax搜索(线段树+动态DP+树剖)

    为什么我怎么看都只会10pts?再看还是只会50~70?只会O(n2(R-L+1))/O(nlogn(R-L+1))……一眼看动态DP可还是不会做…… 根节点的答案是叶子传上来的,所以对于L=R的数据 ...

  2. [ZJOI2019]Minimax搜索

    先求出根节点的权值\(w\).根据套路,我们对于每个\(k\),计算\(w(s)\leq k\)的方案数,差分得到答案.为了方便,接下来考虑计算概率而不是方案数. 可以发现,对于一个给定的有解的子集, ...

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

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

  4. LOJ3044. 「ZJOI2019」Minimax 搜索

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

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

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

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

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

  7. [UOJ#268]. 【清华集训2016】数据交互[动态dp+可删堆维护最长链]

    题意 给出 \(n\) 个点的树,每个时刻可能出现一条路径 \(A_i\) 或者之前出现的某条路径 \(A_i\) 消失,每条路径有一个权值,求出在每个时刻过后能够找到的权值最大的路径(指所有和该路径 ...

  8. uoj#268. 【清华集训2016】数据交互(动态dp+堆)

    传送门 动态dp我好像还真没咋做过--通过一个上午的努力光荣的获得了所有AC的人里面的倒数rk3 首先有一个我一点也不觉得显然的定理,如果两条路径相交,那么一定有一条路径的\(LCA\)在另一条路径上 ...

  9. 洛谷4719 【模板】动态dp

    题目:https://www.luogu.org/problemnew/show/P4719 关于动态DP似乎有猫锟的WC2018论文,但找不见:还是算了. http://immortalco.blo ...

随机推荐

  1. ADO.NET 八(一个例子)

    可视化方式绑定 DataGridView 控件(写的不详细,结合上一篇) 使用可视化数据绑定方式可以快速完成将数据表中的数据显示在 DataGridView 控件中的操作,并可以很容易地对绑定列的属性 ...

  2. angular异步获取数据后在ngOnInit中无法获取,显示undefined解决办法

    两种方法 1 通过*ngif动态加载要数据渲染的dom 2 通过路由导航resolve 第一种感觉太麻烦了,要是一个页面请求多个接口,那就不得不写多个*ngif,本人还是更倾向与第二种发法 具体步骤: ...

  3. python智能提取省、市、区地址

    工具原文 https://github.com/DQinYuan/chinese_province_city_area_mapper 说明: https://blog.csdn.net/qq_3325 ...

  4. SetCurrentCellAddressCore 函数的可重入调用

    绑定数据在线程中 private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e) { if (Di ...

  5. Nacos Docker集群部署

    参考文档:https://nacos.io/zh-cn/docs/quick-start-docker.html 1.从git上下载nacos-docker项目,本地目录为/docksoft/naco ...

  6. PHP开发工具 zend studio

    一.搭建PHP开发环境Apahce服务器Dreamwear创建站点 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional/ ...

  7. 7. Input and Output

    7. Input and Output There are several ways to present the output of a program; data can be printed i ...

  8. kubernetes之pod健康检查

    目录 kubernetes之pod健康检查 1.概述和分类 2.LivenessProbe探针(存活性探测) 3.ReadinessProbe探针(就绪型探测) 4.探针的实现方式 4.1.ExecA ...

  9. 每个程序员都应该知道延迟数—Latency Numbers Every Programmer Should Know

    每个程序员都应该知道延迟数 Latency Numbers Every Programmer Should Know https://people.eecs.berkeley.edu/~rcs/res ...

  10. 给定数字N,输出小于10^N的所有整数

    讲起来比较简单,从0到N遍历输出就行了,但是如果N非常大,就涉及整数溢出问题,很明显是一个全排列问题,也就是输出N,代表N位上所有的数字取值是0-9,做一个全排列,还需要考虑的就是对于0001,006 ...