[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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章