【题解】P6773 命运

题面

题目传送门

前言

前置知识:DP,线段树合并

口齿不清的云落又双叒叕上线了……

第四道黑题~

正文

集训期间,shr 说:“这是一道线段树合并优化 DP”

其实 DP 是很好想到的,毕竟求方案数嘛~

但是怎么 DP,或者说怎么设置状态并转移?

比较朴素的做法:依照题意,列出状态

fx,0/1 表示对于所有满足如下条件的约束 u,v,是/否保证所有约束被满足

条件:对于一组约束 u,v,有 ux 子树外且 vx 子树内

然后……

Game Over!

根本做不下去嘛 qwq

汲取一些失败经验

无法转移,究其原因,是 0/1 维度所包含的信息量过大

树形 DP 有一个很强的性质,由儿子向父亲转移

而对于上述 DP,试图进行一次转移

注意到 (x,fax) 是无法判断的,因为不知道 x 子树内的相关约束是否与边 (x,fax) 有关

回到树形 DP 最基础的性质,由子孙转移向祖先

由深度大的向深度低的转移

嗯——深度?

突然发现一个神奇的小性质

对于一组约束集 (u,v),满足下端点同时为 v,则必须满足深度最大的 u

这个感性理解下即可

那么既然这个性质与 u 的最大深度有关,并结合上文没有前途的 DP 状态

fx,i 表示表示 x 为根的子树中,两端点都在 x 子树内的限制已经被满足,而对于尚未被满足的 (u,v), 且 ux 子树外,vx 子树内,最深的 u 的深度为 i 的方案数

加粗部分是无后效性的一个重要设计

对于未赋值的边,权值赋为 0

可能会有人问,那么 u,v 都在 x 子树外捏?

……那这组约束 (u,v) 根本与 x 子树无关好叭╮(╯-╰)╭

考虑转移

x 的子结点为 y,由 y 转移到 x

大分讨!

  1. (x,y) 赋值为 1

此时 y 子树内所有的约束都将被满足(因为转移的过程中 fay=x),显然有

fx,ij=0depxfx,i×fy,j

  1. (x,y) 赋值为 0
  • 如果 y 中深度最大的约束是 i 的,那么就和第一种情况大差不差,y 子树无法提供更严格的约束,方程如下:

fx,ifx,i×j=0ify,j

  • 否则,y 子树会贡献更严格的约束,最深的限制可以有更浅的限制转移而来,方程如下:

fx,ifx,j×j=0i1fy,i

把三种情况依照加法原理合并,得出:

fx,ifx,i×j=0depxfy,j+fx,i×j=0ify,j+fy,i×j=0i1fx,j

就这么个东西,太抽象了

看到一堆西格玛号,云落就提不起继续做下去的欲望

先给它整理一下,有:

fx,ifx,i×(j=0depxfy,j+j=0ify,j)+fy,i×j=0i1fx,j

注意到西格玛号的结构是极相似的,稍微代换一下

gx,i 表示 j=0ifx,i

原式简化为

fx,ifx,i×gy,depx+fx,i×gy,i+fy,i×gx,i1

至此,我们已经有了一个 O(n2) 的做法,可以拿到 64pts

考虑优化

注意到 g 在式子中表现为前缀和的形式

定义两个“变”量 su,sv,分别记录 gx,i,gy,i 的值

式子是若干项相加,线段树合并可以担任这个艰巨的优化任务

我们尝试将 DP 的第二维用线段树维护

第一项是个常数,第二项可以一边计算一边加入

与模板题(雨天的尾巴)相比,只多了两个系数与一个常数 ——火腿肠

这太抽象了

显然要对 x,y 的取值进行一波分讨

我们看看 merge 函数的具体实现

  1. x,y 都是空结点,略过

  2. x=0,y0,有 su 不会变化,fx,i 全都是 0,让 sv 更新一下,并给 ytag,sum 更新

  3. x0,y=0,同理

  4. 如果 l=r,直接按照上述式子,把后两项加起来,并更新 su,sv

  5. 否则,下传标记,递归地遍历每一棵子树(记得传参 su,sv

  6. 最后将从叶子结点接收到的信息上传,更新答案

  7. 返回合并后新的线段树的根结点编号

最后一个问题

如何统计答案?

显然 f1,0

细节处理:

pushdown 需要注意标记下放的顺序,先乘后加

可以注意一下参考代码中 merge 函数 l=r 的部分,有大坑!

代码

#include<iostream>
#include<vector>
#define int long long
using namespace std;
const int maxn=5e5+10,mod=998244353;
int n,m;
int head[maxn],tot;
struct Edge{
	int to,nxt;
}e[maxn<<1];
int dep[maxn];
vector<int> p[maxn];
int rt[maxn],cnt;
struct Segment_tree{
	struct node{
		int l,r,sum,tag;
	}tr[maxn<<5];
	void pushup(int u){
		tr[u].sum=(tr[tr[u].l].sum+tr[tr[u].r].sum)%mod;
		return;
	}
	void pushdown(int u){
		if(tr[u].tag==1){
			return;
		}
		tr[tr[u].l].sum=tr[u].tag*tr[tr[u].l].sum%mod;
		tr[tr[u].r].sum=tr[u].tag*tr[tr[u].r].sum%mod;
		tr[tr[u].l].tag=tr[u].tag*tr[tr[u].l].tag%mod;
		tr[tr[u].r].tag=tr[u].tag*tr[tr[u].r].tag%mod;
		tr[u].tag=1;
	}
	void modify(int &u,int l,int r,int pos,int k){
		if(u==0){
			u=++tot;
		}
		if(l==r){
			tr[u].tag=1;
			tr[u].sum=k;
			return;
		}
		int mid=l+r>>1;
		pushdown(u);
		if(pos<=mid){
			modify(tr[u].l,l,mid,pos,k);
		}else{
			modify(tr[u].r,mid+1,r,pos,k);
		}
		pushup(u);
		return;
	}
	int query(int u,int l,int r,int ql,int qr){
		if(ql<=l&&qr>=r){
			return tr[u].sum;
		}
		int mid=l+r>>1,res=0;
		pushdown(u);
		if(ql<=mid){
			res=(res+query(tr[u].l,l,mid,ql,qr))%mod;
		}
		if(qr>mid){
			res=(res+query(tr[u].r,mid+1,r,ql,qr))%mod;
		}
		return res;
	}
	int merge(int x,int y,int l,int r,int &su,int &sv){
		if(x==0&&y==0){
			return 0;
		}
		if(x==0){
			sv=(sv+tr[y].sum)%mod;
			tr[y].tag=(tr[y].tag*su)%mod;
			tr[y].sum=(tr[y].sum*su)%mod;
			return y;
		}
		if(y==0){
			su=(su+tr[x].sum)%mod;
			tr[x].tag=(tr[x].tag*sv)%mod;
			tr[x].sum=(tr[x].sum*sv)%mod;
			return x;
		}
		if(l==r){
			int cu=tr[x].sum,cv=tr[y].sum;
			sv=(sv+cv)%mod;
			tr[x].sum=(tr[x].sum*sv+tr[y].sum*su)%mod;
			su=(su+cu)%mod;
			return x;
		}
		int mid=l+r>>1;
		pushdown(x);
		pushdown(y);
		tr[x].l=merge(tr[x].l,tr[y].l,l,mid,su,sv);
		tr[x].r=merge(tr[x].r,tr[y].r,mid+1,r,su,sv);
		pushup(x);
		return x;
	}
}Tr;
inline void add(int u,int v){
	e[++tot].to=v;
	e[tot].nxt=head[u];
	head[u]=tot;
	return;
}
inline void dfs(int u,int fa){
	dep[u]=dep[fa]+1;
	int d=0;
	for(int i:p[u]){
		d=max(d,dep[i]);
	}
	Tr.modify(rt[u],0,n,d,1);
	int su=0,sv=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa){
			continue;
		}
		dfs(v,u);
		su=0;
		sv=Tr.query(rt[v],0,n,0,dep[u]);
		rt[u]=Tr.merge(rt[u],rt[v],0,n,su,sv);
	}
	return;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	for(int i=1;i<=n-1;i++){
		int u,v;
		cin>>u>>v;
		add(u,v);
		add(v,u);
	}
	cin>>m;
	for(int i=1;i<=m;i++){
		int u,v;
		cin>>u>>v;
		p[v].push_back(u);
	}
	dfs(1,0);
	cout<<Tr.query(rt[1],0,n,0,0)<<endl;
	return 0;
}

后记

根本讲不明白啊,总感觉讲得非常糟糕

完结撒花!

posted @   sunxuhetai  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示