[NOI2020]命运

命运

题解

由于uv在树T上的祖先,所以uv的路径一定是树上一条直下的链。

考虑dp,dp_{u,i}表示节点u的子树内边的状态已经确定,且其上端点的深度最深为i时的方案总数。因为一旦一条链未被满足,而u子树内的状态又是已经确定的,故只能在到它祖先的链上出现,而这段距离所有超出路径都会涉及到,如果在其外选必定存在路径未被满足,所以只能在这段上选择,使得所有超出的路径都被满足。

转移方程式也很好想,将儿子节点v合并到父亲节点u时,有:

dp_{u,i}=\sum_{j=0}^{dep_{u}}dp_{u,i}\cdot dp_{v,j}+\sum_{j=0}^{i}dp_{u,i}\cdot dp_{v,j}+ \sum_{j=0}^{i-1}dp_{v,i}\cdot dp_{u,j}

        =dp_{u,i}(sum_{v,dep_{u}}+sum_{v,i})+dp_{v,i}\cdot sum_{u,i-1}(可以通过前缀和进行优化)

发现有许多部分分的n远远大于m,故可以通过虚树进行优化,不过正解不需要这样做

至于前缀和,可以在线段树上进行处理,dp值的更改也可以变成线段树上的操作,于是每次操作就变成了对线段树进行合并。

由于线段树过多,可以采用动态开点,这样也方便了合并的操作。

源码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<map>
using namespace std;
#define MAXN 500005
typedef long long LL;
const LL mo=998244353;
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
void add(LL &x,LL y){x+=y;if(x>=mo)x-=mo;}
int n,m,head[MAXN],dep[MAXN],tot,cnt,root[MAXN];
vector<int>G[MAXN];
struct ming{int lson,rson;LL sum,mul;}tr[MAXN<<5];
struct edge{int to,nxt;}e[MAXN<<1];
void addEdge(int u,int v){e[++tot]=(edge){v,head[u]};head[u]=tot;}
void pushdown(int rt){
	if(tr[rt].lson){
		tr[tr[rt].lson].sum=tr[tr[rt].lson].sum*tr[rt].mul%mo;
		tr[tr[rt].lson].mul=tr[tr[rt].lson].mul*tr[rt].mul%mo;
	}
	if(tr[rt].rson){
		tr[tr[rt].rson].sum=tr[tr[rt].rson].sum*tr[rt].mul%mo;
		tr[tr[rt].rson].mul=tr[tr[rt].rson].mul*tr[rt].mul%mo;
	}
	tr[rt].mul=1;
}
void modify(int &rt,int l,int r,int ai){
	if(!rt)rt=++cnt;tr[rt].sum=tr[rt].mul=1;
	if(l==r)return ;int mid=l+r>>1;
	if(ai<=mid)modify(tr[rt].lson,l,mid,ai);
	else modify(tr[rt].rson,mid+1,r,ai);
}
LL query(int rt,int l,int r,int al){
	if(!rt||r<=al)return tr[rt].sum;
	int mid=l+r>>1;LL res=0;pushdown(rt);
	if(mid<al)add(res,query(tr[rt].rson,mid+1,r,al));
	add(res,query(tr[rt].lson,l,mid,al));
	return res;
}
int merge(int x,int y,int l,int r,LL &s1,LL &s2){
	if(!x&&!y)return 0;
	if(!x||!y){
		add(x?s2:s1,tr[x+y].sum);
		tr[x+y].mul=tr[x+y].mul*(x?s1:s2)%mo;
		tr[x+y].sum=tr[x+y].sum*(x?s1:s2)%mo;
		return x+y;
	}
	if(l==r){
		LL tx=tr[x].sum,ty=tr[y].sum;add(s1,ty);
		tr[x].sum=(tr[x].sum*s1%mo+tr[y].sum*s2%mo)%mo;
		add(s2,tx);return x;
	}
	pushdown(x);pushdown(y);int mid=l+r>>1;
	tr[x].lson=merge(tr[x].lson,tr[y].lson,l,mid,s1,s2);
	tr[x].rson=merge(tr[x].rson,tr[y].rson,mid+1,r,s1,s2);
	tr[x].sum=(tr[tr[x].lson].sum+tr[tr[x].rson].sum)%mo;
	return x;
}
void dfs(int u,int fa){
	dep[u]=dep[fa]+1;int mxd=0,siz=G[u].size();
	for(int i=0;i<siz;i++)mxd=max(mxd,dep[G[u][i]]);
	modify(root[u],0,n,mxd);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;if(v==fa)continue;
		dfs(v,u);LL tmp=query(root[v],0,n,dep[u]),sm=0;
		root[u]=merge(root[u],root[v],0,n,tmp,sm);
	}
}
signed main(){
	read(n);
	for(int i=1;i<n;i++){
		int u,v;read(u);read(v);
		addEdge(u,v);addEdge(v,u);
	}
	read(m);
	for(int i=1;i<=m;i++){
		int u,v;read(u);read(v);
		G[v].push_back(u);
	}
	dfs(1,0);printf("%lld\n",query(root[1],0,n,0));
	return 0;
}

谢谢!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值