P4099 [HEOI2013]SAO

题意#

给你一个有向树形图,求拓扑序的个数。

Solution#

本来想通过拓扑序直接跑 dp,发现假了,然后发现没有利用好这个树形图的性质。

既然是树形图,那我们先不考虑边的方向,当成一棵树。于是我们令 dpi,j 表示以 i 为根的子树中拓扑序中 i 是第 j 个的方案数。然后考虑把 v 合并到根 v 里来。

那我们根据当前边的方向进行讨论:

  1. uv,也就是 u 的拓扑序必然在 v 之前,那么我们枚举一个 k 表示 v 中有多少点在 u 之前。假如说 iu 本来的拓扑序位置,jv 本来的拓扑序位置,那么经过转移 ii+k,而这个 k 必须要满足 k<j,然后用组合数算出两边合并的方案数,得到转移:

dpu,i+k=(i+k1k)×dpu,i×dpv,j×(sizu+sizviksizvk)

  1. vu,也就是 u 的拓扑序必然在 v 之后。类似地我们可以得到一个差不多的转移式,只不过要求 kj

对于上面这个 dp 的转移是 O(n3) 的,但是由于枚举的时候其实中间只有一项和 j 有关,于是前后缀优化一下就可以了。

Code#

#define int long long
using namespace std;
const int MAXN=1010;
const int MOD=1e9+7;
int C[MAXN][MAXN];
void init(){
	C[0][0]=1;rep(i,1,1000){
		C[i][0]=1; rep(j,1,1000)
		C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
	}
}//remember to call it
struct Edge{int to,fl;};
vector<Edge> e[MAXN];
int dp[MAXN][MAXN],tmp[MAXN],siz[MAXN],f[MAXN];
void dfs(int x,int fa){
	dp[x][1]=1;siz[x]=1;
	for(auto s:e[x]){
		if(s.to==fa) continue;
		int v=s.to; dfs(v,x);
		memcpy(f,dp[x],sizeof(f));
		memset(dp[x],0,sizeof(dp[x]));
		memset(tmp,0,sizeof(tmp));
		
		if(!s.fl) rep(j,1,siz[v]) tmp[j]=(tmp[j-1]+dp[v][j])%MOD;
		else per(j,siz[v],1) tmp[j]=(tmp[j+1]+dp[v][j])%MOD;
		
		per(i,siz[x],1) rep(k,0,siz[v]){
			int cur=C[i+k-1][k]*f[i]%MOD*C[siz[x]+siz[v]-i-k][siz[v]-k]%MOD;
			if(!s.fl) (cur*=tmp[k])%=MOD;
			else (cur*=tmp[k+1])%=MOD;
			(dp[x][i+k]+=cur)%=MOD;
		}
		siz[x]+=siz[v];
	}
}
void solve(){
	int n;cin>>n;
	rep(i,1,n-1){
		int u,v;char op;
		cin>>u>>op>>v;u++;v++;
		if(op=='>') swap(u,v);
		e[u].pb(Edge{v,1});
		e[v].pb(Edge{u,0});
	}dfs(1,0);
	int ans=0;
	rep(i,1,n) ans=(ans+dp[1][i])%MOD;
	cout<<ans<<'\n';
	memset(dp,0,sizeof(dp));
	rep(i,1,n) e[i].clear();
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	init();
	int T;for(cin>>T;T--;)solve();
	return 0;
}
posted @   ZCETHAN  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示
主题色彩