洛谷 P4099

emmm... tzc 说是好题,那肯定是好题,因为 tzc 英语带师!

洛谷题目页面传送门

给定一个基图为树的有向图,求它的拓扑序个数。答案对 (10^9+7) 取模。本题多测。

(nin[1,1000],Tin[1,5])

考虑树形 DP。很自然的想到 (dp_{i,j}) 表示子树 (i) 中节点 (i) 在拓扑序列中的序数为 (j) 的拓扑序列数。

那么在转移的时候,要把一个节点的所有儿子都考虑进去,这个极不好控制,是指数级的。不难发现,儿子与儿子之间没有直接联系,只是 (i) 与儿子们各有一个联系。于是可以将儿子一个一个算入转移中,而非同时转移,这样能使时间复杂度得以控制在多项式级别。具体地,大概就加个一维 (dp_{i,j,k})(j) 表示考虑到 (i) 的第 (j) 个儿子了,(k) 为原 (j)。这样看起来状态数增加了一级,其实不然,因为 (sumlimits_{i=1}^n|son_i|=mathrm O(n))。还是 (mathrm O!left(n^2 ight))

目标:(sumlimits_{i=1}^ndp_{1,|son_1|,i});边界:(dp_{i,0,1}=1)

接下来考虑如何转移。当前要计算 (dp_{i,j}),我们设 (h=dp_{i,j},f=dp_{i,j-1},g=dp_{son_{i,j},left|son_{son_{i,j}} ight|}),那么显然是 (f)(g) 共同算出 (h)。然后设 (sz_h,sz_f,sz_g) 分别为所对应的 size。根据 (i)(son_{i,j}) 之间边的方向,分两种情况,分别为在拓扑序中 (son_{i,j})(i) 前和后。现在先讨论前者。

考虑枚举 (h) 中在 (i) 前面有几个 (g) 中的点。那么此时 (i)(f) 中的排名已经确定了,用在 (h) 中的排名减去前面在 (g) 中的点数即可。那么 (son_{i,j})(g) 中的排名呢?它需要在 (i) 前面,于是在 (1) 到「(h) 中在 (i) 前面有几个 (g) 中的点」均可。(f)(g) 内部的排列方案数定下来了,然后再很自然地给 (f)(g) 分配相对位置,(i) 左边和右边各乘上一个组合数即可。于是得到前者的状态转移方程:

[h_i=sum_{j=1}^{sz_g}f_{i-j}sum_{k=1}^jg_kinom{i-1}jinom{sz_h-i}{sz_g-j} ]

类似地可以得到后者的方程(比较恶心一点):

[h_i=sum_{j=1}^{sz_g}f_{i+j-sz_g}sum_{k=sz_g-j+1}^{sz_g}g_kinom{sz_h-i}jinom{i-1}{sz_g-j} ]

此时总时间复杂度是 (mathrm O!left(n^4 ight)) 的(平方状态,平方转移)。考虑优化。

显然,两个方程中的第二个 (sum) 可以用前缀和优化掉,此时三方,继续优化。然后定睛一看:写成前缀和形式之后,展开组合数,发现一派常数一派关于 (j) 一派关于 (i-j),那不就是个卷积吗?!我当时害怕极了,我不会 FFT 呀!而且即使优化了,也是平方对数,还要乘个 (T),也是过不去的样子。

然后发现这个三方的复杂度很虚伪,它实际上是 (mathrm O!left(sumlimits_{i=1}^nsumlimits_{j=1}^{|son_i|}sz_{son_{i,j}}sumlimits_{k=1}^{j-1}sz_{son_{i,k}} ight)),很跑不满三方。然后拿链、菊花、蒲公英、毛毛虫、随机树都试一遍,发现没有一个被卡掉的。事实上它是平方的。下面归纳证明 (mathrm T(n)leq n^2)

  1. 显然 (mathrm T(0)=0)
  2. 假设 (forall iin[0,x),mathrm T(i)leq i^2)。设大小为 (x) 的树的树根的儿子子树大小分别为 (a_{1sim m}),则显然有 (mathrm T(x)=sumlimits_{i=1}^mmathrm T(a_i)+sumlimits_{i=1}^ma_ileft(sumlimits_{j=1}^{i-1}a_j+1 ight))。根据假设有 (mathrm T(x)leq sumlimits_{i=1}^ma_i^2+sumlimits_{i=1}^ma_ileft(sumlimits_{j=1}^{i-1}a_j+1 ight)=sumlimits_{i=1}^ma_ileft(sumlimits_{j=1}^ia_j+1 ight)leq sumlimits_{i=1}^ma_ixleq x^2)

综上,得证。

代码(要很多次取模,常数比较大,要开 (mathrm O_2)):

#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
#define X first
#define Y second
#define pb push_back
const int mod=1000000007;
const int N=1000;
int n;
int comb[N+1][N+1];
vector<pair<int,bool> > nei[N+1];
int dp[N+1][N+2];
int nw[N+1];
int sz[N+1];
void dfs(int x=1,int fa=0){
	sz[x]=1;
	dp[x][1]=1;
	for(int i=0;i<nei[x].size();i++){
		int y=nei[x][i].X;bool z=nei[x][i].Y;
		if(y==fa)continue;
		dfs(y,x);
		sz[x]+=sz[y];
		if(z){
			for(int j=1;j<=sz[y];j++)(dp[y][j]+=dp[y][j-1])%=mod;
			for(int j=1;j<=sz[x];j++)nw[j]=0;
			for(int j=1;j<=sz[x];j++)for(int k=0;k<=sz[y]&&k<j;k++)
				(nw[j]+=1ll*dp[x][j-k]*dp[y][k]%mod*comb[j-1][k]%mod*comb[sz[x]-j][sz[y]-k]%mod)%=mod;
			for(int j=1;j<=sz[x];j++)dp[x][j]=nw[j];
		}
		else{
			for(int j=sz[y];j;j--)(dp[y][j]+=dp[y][j+1])%=mod;
			for(int j=1;j<=sz[x];j++)nw[j]=0;
			for(int j=1;j<=sz[x];j++)for(int k=0;k<=sz[y]&&k<=sz[x]-j;k++)
				(nw[j]+=1ll*dp[x][j+k-sz[y]]*dp[y][sz[y]-k+1]%mod*comb[sz[x]-j][k]%mod*comb[j-1][sz[y]-k]%mod)%=mod;
			for(int j=1;j<=sz[x];j++)dp[x][j]=nw[j];
		}
	}
}
void mian(){
	cin>>n;
	for(int i=1;i<=n;i++)nei[i].clear();
	memset(dp,0,sizeof(dp));
	for(int i=1;i<n;i++){
		int x,y;char z;
		cin>>x>>z>>y;
		x++,y++;
		if(z=='<')nei[x].pb(mp(y,0)),nei[y].pb(mp(x,1));
		else nei[x].pb(mp(y,1)),nei[y].pb(mp(x,0));
	}
	dfs();
	int ans=0;
	for(int i=1;i<=n;i++)(ans+=dp[1][i])%=mod;
	cout<<ans<<"
";
}
int main(){
	comb[0][0]=1;
	for(int i=1;i<=N;i++)for(int j=0;j<=i;j++)comb[i][j]=((j?comb[i-1][j-1]:0)+(j<i?comb[i-1][j]:0))%mod;
	int testnum;
	cin>>testnum;
	while(testnum--)mian();
	return 0;
}
珍爱生命,远离抄袭!
原文地址:https://www.cnblogs.com/ycx-akioi/p/Luogu-P4099.html