题目大意
有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
=
0
m
(
−
1
)
k
C
(
m
,
k
)
(
m
−
k
)
n
=
m
!
S
(
n
,
m
)
f(n,m)=\sum_{k=0}^{m}(-1)^kC(m,k)(m-k)^n=m!S(n,m)
f(n,m)=k=0∑m(−1)kC(m,k)(m−k)n=m!S(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;
}