【HNOI2015】实验比较(树形DP,容斥)

题意:

给你一棵树,你要对所有节点定一个顺序序列,形如 \(p_1 \oplus_1 p_2 \oplus_2 p_3\cdots p_{n-1}\oplus_{n-1} p_n\),其中 \(\oplus_i\)\(=\)\(<\)\(p_{1\sim n}\)\(1\sim n\) 的一个排列, 要满足父亲节点严格小于儿子节点,求不同的序列个数,其中两个序列不同当且仅当存在两个点 \(a,b\) 在两个序列中的关系(\(<,=,>\))不同。

\(n\leq 100\)

题解:

法一:

有点类似拓扑序的 DP?

注意由于一个连续等于段内的点的顺序是无关的,所以我们 DP 的时候只需要记录连续等于段的数量。

\(f_{i,j}\) 表示仅考虑 \(i\) 子树内的点的顺序序列,其中构成了 \(j\) 个连续等于段的方案数。

合并子树时枚举新的连续等于段个数转移即可。时间复杂度为 \(O(n^3)\),类似一个树上背包,只不过转移是 \(O(n)\) 而不是 \(O(1)\) 的。

法二:

考虑将问题转化:现在要求你将每个节点都染上一种颜色,要求父亲节点颜色大于(注意这里为了方便,我们改成了大于)儿子节点颜色,且出现的颜色必须连续,求染色方案数。

考虑枚举出现的颜色的最小值 \(M\),现在转化为:给每个节点染上一种 \([1,M]\) 中的颜色,要求出现的颜色个数恰好为 \(M\),记方案数为 \(g_M\)

考虑容斥,考虑求出 \(f_i\) 表示给每个节点染上一种 \([1,i]\) 中的一种颜色,没有其他限制的方案数。那么:

\[g_M=\sum_{i=0}^M\binom{M}{i}(-1)^{M-i}f_i \]

\(f_i\) 可以容易用树形 DP 求出。时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>

#define N 110
#define fi first
#define se second
#define pii pair<int,int>
#define mk(a,b) make_pair(a,b)

using namespace std;

namespace modular
{
	const int mod=1000000007,inv2=(mod+1)/2;
	inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
	inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
	inline int mul(int x,int y){return 1ll*x*y%mod;}
	inline void Add(int &x,int y){x=x+y>=mod?x+y-mod:x+y;}
	inline void Dec(int &x,int y){x=x-y<0?x-y+mod:x-y;}
	inline void Mul(int &x,int y){x=1ll*x*y%mod;}
}using namespace modular;

inline int poww(int a,int b)
{
	int ans=1;
	while(b)
	{
		if(b&1) Mul(ans,a);
		Mul(a,a);
		b>>=1;
	}
	return ans;
}

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

int n,m,fa[N];
int cnt,head[N],nxt[N],to[N];
int f[N][N],s[N][N],g[N];
int fac[N],ifac[N];
bool vis[N],nrt[N];

int C(int n,int m)
{
	return mul(mul(fac[n],ifac[m]),ifac[n-m]);
}

vector<pii>e;

int find(int x)
{
	return x==fa[x]?x:(fa[x]=find(fa[x]));
}

void adde(int u,int v)
{
	nrt[v]=1;
	to[++cnt]=v;
	nxt[cnt]=head[u];
	head[u]=cnt;
}

void dfs(int u)
{
	if(vis[u])
	{
		puts("0");
		exit(0);
	}
	vis[u]=1;
	for(int j=1;j<=n+1;j++) f[u][j]=1;
	for(int i=head[u];i;i=nxt[i])
	{
		int v=to[i];
		dfs(v);
		for(int j=1;j<=n+1;j++)
		{
			if(u!=n+1) Mul(f[u][j],s[v][j-1]);
			else Mul(f[u][j],s[v][j]);
		}
	}
	for(int j=1;j<=n+1;j++)
		s[u][j]=add(s[u][j-1],f[u][j]);
}

int main()
{
//	freopen("1.in","r",stdin);
//	freopen("1_my.out","w",stdout);
	n=read(),m=read();
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++)
	{
		int u=read();
		char c=getchar();
		int v=read();
		if(c=='<') e.push_back(mk(u,v));
		else
		{
			int a=find(u),b=find(v);
			if(a!=b) fa[a]=b;
		}
	}
	for(auto ne:e) adde(find(ne.fi),find(ne.se));
	for(int i=1;i<=n;i++)
		if(fa[i]==i&&!nrt[i]) adde(n+1,i);
	dfs(n+1);
	for(int i=1;i<=n;i++)
	{
		if(fa[i]==i&&!vis[i])
		{
			puts("0");
			return 0;
		}
	}
	fac[0]=1;
	for(int i=1;i<=n+1;i++) fac[i]=mul(fac[i-1],i);
	ifac[n+1]=poww(fac[n+1],mod-2);
	for(int i=n+1;i>=1;i--) ifac[i-1]=mul(ifac[i],i);
	int ans=0;
	for(int i=1;i<=n+1;i++)
	{
		for(int j=1;j<=i;j++)
		{
			if((i-j)&1) Dec(g[i],mul(C(i,j),s[n+1][j]));
			else Add(g[i],mul(C(i,j),s[n+1][j]));
		}
		Add(ans,g[i]);
	}
	printf("%d\n",mul(ans,inv2));
	return 0;
}
posted @ 2022-10-28 20:47  ez_lcw  阅读(19)  评论(0编辑  收藏  举报