[ZOJ-4084] ZOJ Monthly Jan 2019- D - Little Sub and Heltion's Math Problem

题目大意

有n个粉丝,有m个队伍
需要满足:
1.对于任何一个粉丝,他至少是一个队伍的粉丝,但是他不能是所有队伍的粉丝。
2.对于任意的队伍i和队伍j,恰好存在一个队伍k的粉丝恰好队伍i和队伍j的并集(ijk可以相同)
3.对于任意的队伍i和队伍j,恰好存在一个队伍k的粉丝恰好队伍i和队伍j的交集(ijk可以相同)

思路分析

(看完题目莫名想到离散数学中的偏序关系)
我们假设每一个队伍的粉丝都是一个集合。
因为集合存在交集、并集。我们可以假装来“差分”一下这些集合。
每个队伍只记录这个队伍比“子队伍”多的部分。
(一个圆圈代表一个队伍,圆圈内的是差分的粉丝集合,圆圈外是队伍实际的粉丝集合)
在这里插入图片描述
如果你能大概猜想图长得大概像这上面一样,那就好了,(因为我不太会解释为什么,或许是从给的条件里面看出来的)

我们逐个来解析这三个条件。
(直接按照顺序的话可能不一定能马上想出来,我们可以分解条件,乱序使用这些条件)

  • 条件1的前半句意味着所有的n个粉丝都应该要出现(所有粉丝都是最“上”方的队伍的粉丝)
  • 条件1的后半句意味着最“下”方的队伍的粉丝集合一定是空集(因为这是一个“差分”集合)
  • 条件2和条件3的第一个恰好,组合起来也就意味着这个图应该恰好有m个圆圈(m个队伍)
  • 条件2的第二个恰好
    假设这两个队伍是父子关系(i是j的“父队伍”),那么k显然就是i
    假设这两个队伍是并列关系,那么队伍k是队伍i和j的“父队伍”,并且所在的“差分集合”是空集
    同时最多只能2叉
  • 条件3的第二个恰好
    假设这两个队伍是父子关系(i是j的“父队伍”),那么k显然就是i
    假设这两个队伍是并列关系,那么队伍k是队伍i和j的“父队伍”,并且所在的“差分集合”是空集
    同时最多只能2叉

通过这些条件,我们就可以在草稿纸上画出“偏序关系”(差分集合)的可能的形状了。
(最主要的是题目说m的范围是2到6,很少,而且条件很苛刻)
在这里插入图片描述
其实所有可能的情况也就是这么些。
黑色实心的队伍的“差分集合”,必须是空集。(因为他是最“下方”的队伍)
红色实心的队伍的“差分集合”,必须是空集。(因为他是一个二叉的“父队伍”)

基本的形状有了,我们要做就是计算分配这些粉丝的方案数了。
(因为答案=队伍的偏序关系数(考虑顺序)*分配粉丝使得满足这个形状的方案数)

分配粉丝使得满足这个形状的方案数

我们来品一品这句话
我们一共有n个粉丝要分配,一共要分配到m个队伍(此处m仅仅是一个字母而已,非题目输入的m),并且每个队伍都不能分到0个粉丝的方案数。
似乎有些熟悉?

  • n个小球分配到m个箱子,每个箱子不为空的方案数。
  • n个元素映射到m个元素,每个元素都要被映射到(满射、到上(onto)函数)的函数个数。

根据离散数学的知识(比如容斥原理),我们就可以计算出:
f(n,m)=k=0m(1)kC(m,k)(mk)n=m!S(n,m)f(n,m)=\sum_{k=0}^{m}(-1)^kC(m,k)(m-k)^n=m!S(n,m)
其中S(n,m)S(n,m)表示第二类斯特林数

到此,我们解决了所有的问题,这道题就做出来了。
(什么?没有解释“队伍的偏序关系数(考虑顺序)”?其实就是原本那个形状*m!/等价的点,纯粹组合数学知识,相信你应该会)

代码实现

#include<cstdio>
#include<iostream>
typedef long long LL;
const int MOD =1e9+7;
using namespace std;
LL jie[10];
void Madd(LL &a,LL b){//为了方便,直接弄一个取模加法
	a=((a+b%MOD)%MOD+MOD)%MOD;
}
LL ksm(LL a,LL b){
	LL ans=1;
	for(;b;b>>=1,a=a*a%MOD)
		if(b&1)ans=ans*a%MOD;
	return ans; 
}
int C(int n,int m){//m很小,直接排列组合
	LL ans=1;
	for(int i=1;i<=n;i++)ans*=i;
	for(int i=1;i<=m;i++)ans/=i;
	for(int i=1;i<=n-m;i++)ans/=i;
	return ans;
}
LL f(LL n,int m){//函数f(n,m)
	LL ans=0;
	for(int k=0;k<=m;k++)
		if(k&1)Madd(ans,-C(m,k)*ksm(m-k,n));
		else Madd(ans,C(m,k)*ksm(m-k,n));
	return ans;
}
void work(){
	LL n,m,ans=0;
	cin>>n>>m;
	switch(m){
		case 2:
			Madd(ans,jie[2]*f(n,1));
			break;
		case 3:
			Madd(ans,jie[3]*f(n,2));
			break;
		case 4:
			Madd(ans,jie[4]*f(n,3));
			Madd(ans,jie[4]/2*f(n,2));
			break;
		case 5:
			Madd(ans,jie[5]*f(n,4));
			Madd(ans,2*jie[5]/2*f(n,3));
			break;
		case 6:
			Madd(ans,jie[6]*f(n,5));
			Madd(ans,3*jie[6]/2*f(n,4));
			Madd(ans,jie[6]*f(n,3));
			break;
	}
	printf("%lld\n",ans);
}
int main() {
	jie[1]=1;
	for(int i=2;i<=6;i++)
		jie[i]=jie[i-1]*i%MOD; 
	int T;
	cin>>T;
	while(T--)work();
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章