YbtOJ#20081-[NOIP2020模拟赛B组Day8]树上排列【组合数,树形dp】

正题

题面链接:https://www.ybtoj.com.cn/contest/62/problem/3


题目大意

n n n个点的一棵树,每个边的边会表示一个大小关系(如 p x > p y p_x>p_y px>py p x < p y p_x<p_y px<py)。求有多少个排列满足所有条件。


解题思路

考虑树形 d p dp dp,设 f i , j f_{i,j} fi,j表示点 i i i的子树中有 j j j个比他小的数字时的方案数。

那么如果有条件 p y < p x p_y<p_x py<px考虑如何转移,我们枚举一下 i , j i,j i,j表示之前比 i i i小的数的个数和 y y y里面比 x x x小的个数,然后再枚举一个 k k k表示 y y y里面比 y y y小的数的个数。
然后用组合数插板表示方案,发现这样是 O ( n 3 ) O(n^3) O(n3)的,发现 k k k可以前/后缀和优化,所以可以缩掉,时间复杂度为 O ( n 2 ) O(n^2) O(n2)


c o d e code code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=5100,XJQ=998244353;
struct node{
	ll to,next,w;
}a[N*2];
ll n,tot,ls[N],c[N][N],f[N][N],g[N],siz[N],ans;
ll C(ll n,ll m){return c[n+1][m+1];}
void addl(ll x,ll y,ll w){
	a[++tot].to=y;
	a[tot].next=ls[x];
	ls[x]=tot;a[tot].w=w;
	return;
}
void dfs(ll x,ll fa){
	f[x][0]=1;siz[x]=1;
	for(ll p=ls[x];p;p=a[p].next){
		ll y=a[p].to;
		if(y==fa)continue;
		dfs(y,x);
		for(ll i=0;i<=siz[x]+siz[y];i++)g[i]=0;
		if(a[p].w){
			for(ll i=0;i<siz[x];i++){
				ll tmp=0;
				for(ll j=siz[y]-1;j>=0;j--){
					ll px=i,py=j;tmp=(tmp+f[y][j])%XJQ;
					ll sx=siz[x]-i-1,sy=siz[y]-j-1;
					(g[siz[x]+siz[y]-sx-sy-2]+=f[x][i]*tmp%XJQ*C(sx+sy+1,sx)%XJQ*C(px+py,py)%XJQ)%=XJQ;
				}
			}
		}
		else{
			for(ll i=0;i<siz[x];i++){
				ll tmp=0;
				for(ll j=0;j<siz[y];j++){
					ll px=i,py=j;tmp=(tmp+f[y][j])%XJQ;
 					ll sx=siz[x]-i-1,sy=siz[y]-j-1;
					(g[px+py+1]+=f[x][i]*tmp%XJQ*C(px+py+1,px)%XJQ*C(sx+sy,sy)%XJQ)%=XJQ;
				}
			}
		}
 		siz[x]+=siz[y];
		for(ll i=0;i<=siz[x];i++)f[x][i]=g[i];
	}
	return;
}
int main()
{
	freopen("perm.in","r",stdin);
	freopen("perm.out","w",stdout);
	scanf("%lld",&n);
	for(ll i=1;i<n;i++){
		ll x,y;scanf("%lld%lld",&x,&y);
		addl(x,y,0);addl(y,x,1);
	}
	c[0][0]=1;
	for(ll i=1;i<N;i++)
		for(ll j=1;j<N;j++)
			c[i][j]=(c[i-1][j]+c[i-1][j-1])%XJQ;
	dfs(1,1);
	for(ll i=0;i<siz[1];i++)
		(ans+=f[1][i])%=XJQ;
	printf("%lld\n",ans);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值