洛谷 P4099 [HEOI2013] SAO

题目链接

\(f _ {x, i}\) 表示 \(x\) 节点在子树内的排名为 \(i\) 的方案数。类似树形背包,依次枚举 \(x\) 的所有子节点 \(y\),并把 \(y\) 加入 \(x\) 中:

对于 \(p _ x < p _ y\) 的情况,枚举 \(f _ {x, i}\)\(f _ {y, j}\) 合并,但这样是不够的,还要再枚举一个 \(k\)\(k < j\))表示 \(y\) 子树中有 \(k\) 个节点比 \(x\) 小。方案数即对于 \(x\) 前后分别插板,为 \(\displaystyle \binom {i - 1 + k} k \binom {s _ x - i + s _ y - k} {s _ y - k}\)。发现这与 \(j\) 无关,于是对 \(f _ {y, j}\) 做后缀和转移即可。

对于 \(p _ x > p _ y\) 的情况同上。时间复杂度 \(\text O (T n ^ 2)\)

#include<cstdio>
#include<cstring>
#define N 1005
using namespace std;

const int mod=1e9+7;
int T,n,ans,siz[N],C[N][N],f[N][N],g[N][N],h[N][N];
int tot,head[N],nxt[N*2],ver[N*2],e[N*2];
inline void add(int &x,long long y) {x=(x+y)%mod;}
void init(int n) {
	for(int i=0;i<=n;i++)
		for(int j=C[i][0]=1;j<=i;j++)
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
void insert(int x,int y,int z) {
	ver[++tot]=y,e[tot]=z,nxt[tot]=head[x],head[x]=tot;
}
void dp(int x,int fa) {
	siz[x]=1,f[x][1]=1;
	for(int i=head[x],y;i;i=nxt[i]) {
		if((y=ver[i])==fa) continue;
		dp(y,x);
		for(int j=siz[x];j;j--) {
			int w=f[x][j]; f[x][j]=0;
			if(e[i])
				for(int k=0;k<siz[y];k++) {
					add(f[x][j+k],1ll*w*C[j-1+k][k]%mod*C[siz[x]-j+siz[y]-k][siz[y]-k]%mod*h[y][k+1]);
				}
			else
				for(int k=1;k<=siz[y];k++) {
					add(f[x][j+k],1ll*w*C[j-1+k][k]%mod*C[siz[x]-j+siz[y]-k][siz[y]-k]%mod*g[y][k]);
				}
		}
		siz[x]+=siz[y];
	}
	for(int i=1;i<=siz[x];i++)
		g[x][i]=(g[x][i-1]+f[x][i])%mod;
	for(int i=siz[x];i;i--)
		h[x][i]=(h[x][i+1]+f[x][i])%mod;
}
int main() {
	init(1000),scanf("%d",&T);
	XJX:while(T--) {
		tot=0;
		for(int i=0;i<n;i++) head[i]=0;
		scanf("%d",&n);
		for(int i=1,x,y;i<n;i++) {
			char c; scanf("%d %c %d",&x,&c,&y);
			int z=c=='<'; insert(x,y,z),insert(y,x,!z);
		}
		memset(f,0,sizeof(f)),memset(g,0,sizeof(g)),memset(h,0,sizeof(h));
		dp(0,-1),ans=0;
		for(int i=1;i<=n;i++) add(ans,f[0][i]);
		printf("%d\n",ans);
	}
	return 0;
}
posted @ 2026-01-15 21:04  yemuzhe  阅读(2)  评论(0)    收藏  举报