【组合计数】BZOJ4013 [HNOI2015]实验比较

【题目】
BZOJ
n n n个物品和 m m m个质量关系(小于或等于),每个物品至多有一个小于关系(即比某个物品质量小),求满足所有关系的质量序列数。 n ≤ 100 n\leq 100 n100

【解题思路】
题目给定的关系满足每个点至多有一条入边,则合法方案一定是一棵森林。
不妨将所有相等的点合起来,然后建立虚根跑树 DP \text{DP} DP

f i , j f_{i,j} fi,j表示以 i i i为根的子树分成 j j j个不等段( j − 1 j-1 j1个小于号)的方案,那么转移就是要将两个长分别为 a , b a,b a,b的序列合并。显然合并后长度 c c c,满足 max ⁡ ( a , b ) ≤ c ≤ a + b \max(a,b)\leq c\leq a+b max(a,b)ca+b。同时有 f i , c = f v 1 , a ∗ f v 2 , b ∗ ( c a ) ∗ ( a b − ( c − a ) ) f_{i,c}=f_{v1,a}*f_{v2,b}*{c\choose a}*{a\choose b-(c-a)} fi,c=fv1,afv2,b(ac)(b(ca)a)

这个柿子的含义是先在 c c c个位置中选 a a a个,那么剩下 c − a c-a ca个就放第二个序列中的数。接着从 b b b个数中选出 b − ( c − a ) b-(c-a) b(ca)个数字需要与 a a a中的数字进行合并。

复杂度 O ( n 3 ) O(n^3) O(n3),因为每一对点只会在 LCA \text{LCA} LCA处被 DP \text{DP} DP到。

【参考代码】

#include<bits/stdc++.h>
#define mkp make_pair
#define fi first
#define se second
using namespace std;

typedef long long ll;
typedef pair<int,int> pii;
const int N=105,mod=1e9+7;
int n,m,tot,cnt,ans;
int head[N],fa[N],siz[N],du[N],C[N][N],f[N][N],g[N];
pii p[N];

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}

struct Tway{int v,nex;}e[N<<1];
void add(int u,int v){e[++tot]=(Tway){v,head[u]};head[u]=tot;}
int findf(int x){return fa[x]==x?x:fa[x]=findf(fa[x]);}

void dfs(int x)
{
	f[x][0]=1;
	for(int i=head[x];i;i=e[i].nex)
	{
		int v=e[i].v;dfs(v);siz[x]+=siz[v];
		for(int j=0;j<=siz[x];++j) for(int k=1;k<=siz[v];++k)
		{
			for(int l=max(j,k);l<=min(n,j+k);++l)
			{
				int now=1ll*C[l][j]*C[j][k-(l-j)]%mod;
				now=1ll*now*f[x][j]%mod*f[v][k-1]%mod;
				g[l]+=now;g[l]%=mod;
			}
		}
		for(int j=0;j<=siz[x];++j) f[x][j]=g[j],g[j]=0;
	}
	++siz[x];
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("BZOJ4013.in","r",stdin);
	freopen("BZOJ4013.out","w",stdout);
#endif
	n=read();m=read();
	for(int i=0;i<=n;++i)
	{
		fa[i]=i;C[i][i]=C[i][0]=1;
		for(int j=1;j<i;++j) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
	}
	for(int i=1;i<=m;++i)
	{
		int x,y;char ch[2];
		x=read();scanf("%s",ch);y=read();
		if(ch[0]=='=') fa[findf(x)]=findf(y);
		else p[++cnt]=mkp(x,y);
	}
	for(int i=1;i<=cnt;++i) add(findf(p[i].fi),findf(p[i].se)),du[findf(p[i].se)]++;
	cnt=0;
	for(int i=1;i<=n;++i) if(findf(i)==i) 
	{
		if(du[i]==0) add(n+1,i);
		++cnt;
	}
	dfs(n+1);
	for(int i=1;i<=n;++i) ans=(ans+f[n+1][i])%mod;
	printf("%d\n",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值