问题 : 集合划分问题
题目描述
设S是一个具有n个元素的集合,S={a1,a2,……,an},现将S划分成k个满足下列条件的子集合S1,S2,……,Sk ,且满足:
则称 S1 , S2 , …… , Sk 是集合 S 的一个划分。它相当于把 S 集合中的 n 个元素 a1 , a2 , …… , an 放入 k 个( 0 < k≤n < 30 )无标号的盒子中,使得没有一个盒子为空。请你确定 n 个元素 a1 , a2 , …… , an 放入 k 个无标号盒子中去的划分数 S(n , k) 。
输入
输出
输出划分数
样例输入
23 7
样例输出
4382641999117305
思路:之前讲了可以用状态叠加的方法直接叠加每种独立的状态,形成独立的不重复的解,还有一种方法也可以求出此题,先算出所有的方案,再把不可能的方案剪掉,剩下可能但是重复的方案,再除以重复的次数,便可以得出唯一的解,接下来说明怎么求出这个解。由于比较难讲,我先贴代码。
#include <iostream>
#include<algorithm>
#include<cmath>
using namespace std;
long double jc(int k) //阶乘
{
int t;
long double a=1;
for(t=1;t<=k;t++)
a*=t;
return a;
}
long double C(int n,int m)//组合数
{
long double a=1;
int t;
for(t=1;t<=n;t++)
a=a*(m+1-t)/t;
return a;
}
long double mpow(int k,int n)//次方
{
long double a=1;
int t;
for(int t=1;t<=n;t++)
a*=k;
return a;
}
long double count(int n,int k)
{
int t;
long double a;
a=mpow(k,n); //所有情况都先算出来,有重复的
for(t=1;t<k;t++) //减去不可能的情况
a-=count(n,t)*C(t,k);
return a;
}
int main()
{
int n,k;
cin >> n >> k;
long double a;
a=count(n,k);
a=a/jc(k); //除以重复的情况
printf("%lldd\n",(long long)a);
return 0;
}
我们把元素一个一个拿出来,如果是所有解的话每个元素都有k个空间可以存放,有n个元素,所以所有的情况就是n^k,这是包含了不可能的解和重复的解的。
count(n,k)是k个容器里放了n个元素的所有情况。
接下来我们先把不可能的情况减去,由于之前a=mpow(k,n);我们是有可能把所有元素都放在一个容器里,每个容器都有这种情况,假设有7个盘子23个元素,我们把23个元素都放在第一个容器里,把23个元素都放在第二个容器里,都放在第三个容器。。。都放在第七个容器里,由于题目要求每个容器里都至少要有一个元素,所以这些情况都不可能,所以要减去count(n,1)*C(7,1);C(m,n)是组合数,代表了从m个容器里选n个有几种不同方法,同理,也有可能把所有元素都放在两个容器里,那又要减去count(n,2)*C(7,2),因为count(n,2),是两个容器里所有的元素存放的情况了,只是选的两个容器不同而已,这样一直减到count(n,6)*C(7,6);把所有的不可能情况才能减光;
得出通项公式:
Ct(n,k)=k^n-count(n,1)*C(k,1)-count(n,2)*C(k,2)-count(n,3)*C(k,3)-......-count(n,6)*C(k,k-1);这是包含了重复的情况的。
接下来就该考虑怎么消除重复的情况了。
接下来先看几个例子,比如三个元素放在两个容器里。
假设我们先把元素分成两部分,分成1和2,3;
按照之前的算法是有两种情况的。
1 | 2,3 |
第一个容器 | 第二个容器 |
2,3 | 1 |
第一个容器 | 第二个容器 |
所以要除以二,实质上看其实元素的排列方式是没变的,只是下面的容器序号变了一下,那么k个容器序号有几种不同的变法呢?显然是k!种所以要除以k!这样剩下的就是唯一的解了。可能说的还不够详细,日后再补充。