[NOI2020] 命运

前言

菜就多练练。

题目

洛谷

讲解

直接考虑 dp。

dpx,i 表示 x 子树已经考虑完了,目前(下端在子树内)没处理的链的最深的上端深度为 i

对于其儿子 v,有转移:

dpx,ij=0depthxdpx,i×dpv,j+j=0idpx,i×dpv,j+j=0i1dpv,i×dpx,j

显然可以前缀和优化:

dpx,idpx,i×(prev,depthx+prev,i)+prex,i1×dpv,i

某个大佬说过(忘了是谁了),树上跟深度有关的东西都可以线段树合并,这道题也不例外。

对于 prev,depthx 我们可以提前查好,而其它的可以在合并时先递归左子树,后递归右子树,在合并过程中可以顺便统计前缀和。

时空复杂度 O(nlog2n)

代码

洛谷rk2
//12252024832524
#include <bits/stdc++.h>
#define TT template<typename T>
using namespace std; 

typedef long long LL;
const int MAXN = 500005;
const int MOD = 998244353;
int n;

LL Read()
{
	LL x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

int head[MAXN],etot;
struct edge{
	int v,nxt;
}e[MAXN<<1];
void Add_Edge(int u,int v){
	e[++etot] = edge{v,head[u]};
	head[u] = etot;
}
void Add_Double_Edge(int u,int v) {
	Add_Edge(u,v);
	Add_Edge(v,u);
}

#define lc (t[x].ch[0])
#define rc (t[x].ch[1])
int rt[MAXN],tot;
struct node{
	int ch[2],s,mul;
}t[MAXN*40];
void calc(int x,int val){
	if(!x) return;
	t[x].mul = 1ll * t[x].mul * val % MOD;
	t[x].s = 1ll * t[x].s * val % MOD;
}
void down(int x){
	if(t[x].mul != 1){
		calc(lc,t[x].mul);
		calc(rc,t[x].mul);
		t[x].mul = 1;
	}
}
void Add(int &x,int l,int r,int pos){
	x = ++tot; t[x].s = t[x].mul = 1;
	if(l == r) return;
	int mid = (l+r) >> 1; down(x);
	if(pos <= mid) Add(lc,l,mid,pos);
	else Add(rc,mid+1,r,pos);
}
/*
s1 <- s1 + dp[y][i]
dp'[x][i] <- dp[x][i]*s1 + dp[y][i]*s2
s2 <- s2 + dp[x][i]
*/
int mge(int x,int y,int l,int r,int &s1,int &s2){//新写法 get!
	if(!x && !y) return 0;
	if(!x || !y){
		if(y){
			s1 = (s1 + t[y].s) % MOD;
			calc(y,s2);
			return y;
		}
		else{
			s2 = (s2 + t[x].s) % MOD;
			calc(x,s1);
			return x;
		}
	}
	if(l == r){
		s1 = (s1 + t[y].s) % MOD;
		int tmp2 = t[x].s;
		t[x].s = (1ll * t[x].s * s1 + 1ll * t[y].s * s2) % MOD;
		s2 = (s2 + tmp2) % MOD;
		return x;
	}
	down(x); down(y);
	int mid = (l+r) >> 1; 
	lc = mge(lc,t[y].ch[0],l,mid,s1,s2);
	rc = mge(rc,t[y].ch[1],mid+1,r,s1,s2);
	t[x].s = (t[lc].s + t[rc].s) % MOD;
	return x;
}
int Query(int x,int l,int r,int qr){
	if(!x) return 0;
	if(r <= qr) return t[x].s;
	down(x);
	int mid = (l+r) >> 1,ret = 0;
	if(mid+1 <= qr) ret += Query(rc,mid+1,r,qr);
	ret += Query(lc,l,mid,qr);
	return ret % MOD;
}

int d[MAXN];
void dfs1(int x,int fa){
	d[x] = d[fa] + 1;
	for(int i = head[x],v; i ;i = e[i].nxt)
		if((v = e[i].v) ^ fa) dfs1(v,x);
}
int MAX[MAXN];
void dfs2(int x){
	Add(rt[x],0,n,MAX[x]);
	for(int i = head[x],v; i ;i = e[i].nxt)
		if(d[v = e[i].v] > d[x]){
			dfs2(v);
			int s1 = Query(rt[v],0,n,d[x]),s2 = 0;
			rt[x] = mge(rt[x],rt[v],0,n,s1,s2);
		}
}

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n = Read();
	for(int i = 1;i < n;++ i) Add_Double_Edge(Read(),Read());
	dfs1(1,0);
	for(int m = Read(); m ;-- m){
		int u = Read(),v = Read();
		MAX[v] = Max(MAX[v],d[u]);
	}
	dfs2(1);
	Put(Query(rt[1],0,n,0),'\n');
	return 0;
}
posted @   皮皮刘  阅读(56)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示