排列组合问题小总结

排列组合问题抽象化:将M个果子分为N垛

分情况讨论:


类型一:N个相同的果子+M个相同的垛(不允许存在空垛)

例题:(洛谷P1025)将整数 n 分成 k 份,且每份不能为空,任意两个方案不相同(不考虑顺序)。例如:n=7,k=3,下面三种分法被认为是相同的:(1、1、5);(1、5、1);(5、1、1)。问有多少种不同的分法。

代码展示:

#include
#include
long long dp[15][15];//dp[i][j]指i个果子刚好分为j个垛 
long long ans=0;
long long jg[25];
int main()
{
	int m,n;int i,j,t;int T;
	dp[0][0]=1;
	for(i=1;i<=10;i++)
	{
		for(j=1;j<=i;j++)
		{
			dp[i][j]=0;
			for(t=0;t<=j;t++)
			{
				//1、将每一垛铺一个果子
				//2、剩余i-j个果子分成t垛 
				dp[i][j]+=dp[i-j][t];
			}
			
		}
	}
	scanf("%d",&T);
	for(j=1;j<=T;j++)
	{
		scanf("%d %d",&m,&n);
		ans=0;
	    for(i=1;i<=n;i++) 
	    {
	    	ans+=dp[m][i];
     	}
     	jg[j]=ans;
	}
	for(j=1;j<=T;j++) 
	{
		printf("%lld",jg[j]);
		if(j!=T) printf("\n");
	}
	return 0;
}

方法总结:

  1. 1.设dp[i][j],表示将i个果子恰好分为j垛;
  2. 2.将M个垛各铺一个果子;
  3. 3.将剩余的(i-j)个果子分为t垛。

类型二:N个相同的果子+M个相同的垛(允许存在空垛)

例题:(洛谷P2386)把 m个同样的苹果放在 n 个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法。(5,1,1 和 1,1,5 是同一种方法)

代码展示:

#include
#include 
long long dp[205][10];//dp[i][j] 将i个果子分成j垛有多少种方案 
int main()
{
	int n,k;int i,j,t;
	scanf("%d %d",&n,&k);
	dp[0][0]=1;//便于后面的计算 
	for(i=1;i<=200;i++)
	{
		for(j=1;j<=6&&j<=i;j++)
		{
			dp[i][j]=0;//初始化 
		
			for(t=0;t<=j;t++)
			{
				//1、先将每一垛铺上一个果子
		     	//2、 将剩余的i-j个果子分为t垛
			    dp[i][j]+=dp[i-j][t];	
			}
		}	
	}
	printf("%lld",dp[n][k]); 
	return 0;
}

方法总结:

  1. 1.转化为类型一(非空);
  2. 2.答案=刚好分为1垛+刚好分为2垛+刚好分为3垛……+刚好分为n垛。

类型三:N个不同的果子+M个不同的垛(不允许存在空垛)

例题:(洛谷P1287)现有 r个互不相同的盒子和 n个互不相同的球,要将这 n个球放入 r个盒子中,且不允许有空盒子。请求出有多少种不同的放法。两种放法不同当且仅当存在一个球使得该球在两种放法中放入了不同的盒子。

代码展示:

#include
#include
long long a[15];//a[i]至少空了i个盒子的分配情况 
long long c[15][15];//组合数 
int main()
{
	int n,r;int i,j,t;int y;
	long long ans=0;
	scanf("%d %d",&n,&r);
	c[1][0]=1;c[1][1]=1;
	for(i=2;i<=10;i++)
	{
		c[i][0]=1;
		for(j=1;j<=i;j++)
		{
			c[i][j]=c[i-1][j]+c[i-1][j-1];
		}
	}
	for(i=0;i<=r-1;i++)
	{
		//a[i]指至少空了i个盒子
		a[i]=c[r][i];
		for(j=1;j<=n;j++) a[i]=a[i]*(r-i); 
	}
	for(i=0;i<=r-1;i++)
	{
		//容斥原理 
		if(i%2==0) ans+=a[i];
		else ans-=a[i];
	}
	printf("%lld",ans);
	return 0;
} 

方法总结:

  1. a[i]表示至少空了i垛的分配情况;
  2. a[i]=c[r][i]*(r-i)^n:先选出i个“一定不放的垛”(c[r][i]表示从r个种选出i个的组合数),再一个一个球放置,每个球有(r-i)种选择;
  3. 容斥原理:答案=a[0]-a[1]+a[2]-a[3]+a[4]-……+(-1)^n*a[n]。

类型四:N个不同的果子+M个相同的垛(不允许空垛)

例题:(洛谷p1665小朋友的球)@发源于小朋友最近特别喜欢球。有一天他脑子抽了,从口袋里拿出了 N 个不同的球,想把它们放到 M个相同的盒子里,并且要求每个盒子中至少要有一个球,他好奇有几种放法,于是尝试编程实现,但由于他天天不好好学习,只会上 B 站看游泳教练,于是他向你求助。

代码展示:

#include
#include
struct big
{
	int cd;
	long long a[505];
};
struct big f[105][105];//f[i][j]指将i个果子分成j垛 
struct big z;
int main()
{
	int N,M;int i,j,t,k,zj,jw; 
	f[1][1].a[1]=1;f[1][1].cd=1;
	f[2][1].a[1]=1;f[2][1].cd=1;
	f[2][2].a[1]=1;f[2][2].cd=1;
	for(i=2;i<=100;i++)
	{
		for(j=1;j<=i;j++)
		{
			//核心公式:f[i][j]=f[i-1][j]*j+f[i-1][j-1];
			//翻译:如果前i-1个果子分为j垛,那么第i个果子加入哪一垛有j种选择
			//翻译:如果前i-1个果子分为j-1垛,那么第i个果子必须单独成一垛 
			zj=f[i-1][j].cd;
			jw=0;
			for(t=1;t<=100;t++) z.a[t]=0;
			for(t=1;t<=zj;t++)
			{
				z.a[t]=f[i-1][j].a[t]*j;
				z.a[t]+=jw;
				jw=z.a[t]/10;
				z.a[t]%=10;
			}
			t--;
			while(jw)
			{
				t++;
				z.a[t]=jw%10;
				jw/=10;
			}
			z.cd=t;
			zj=t;
			if(f[i-1][j-1].cd>zj) zj=f[i-1][j-1].cd;
			jw=0;
			for(t=1;t<=zj;t++)
			{
				f[i][j].a[t]=z.a[t]+f[i-1][j-1].a[t];
				f[i][j].a[t]+=jw;
				jw=f[i][j].a[t]/10;
				f[i][j].a[t]%=10;
			}
			t--;
			while(jw)
			{
				t++;
				f[i][j].a[t]=jw%10;
				jw/=10;
			}
			f[i][j].cd=t;
		}
	}
	while(scanf("%d %d",&N,&M)!=EOF)
	{
		zj=f[N][M].cd;
		if(zj==0) printf("0");//!注意答案可能为0 单独表示 
		else for(t=zj;t>=1;t--) printf("%lld",f[N][M].a[t]);
		printf("\n");
	}
	return 0;
}

方法总结:

  1. 设f[i][j]指将i个果子分为j垛的分配情况;
  2. 核心公式:f[i][j]=f[i-1][j]*j+f[i-1][j-1];
  3. 进一步阐释(2):若前i-1个果子分为j垛,那么第i个果子加入哪一垛有j种选择;若前i-1个果子分为j-1垛,那么第i个果子必须单独成一垛。

 

你可能感兴趣的:(算法)