*【洛谷 - P1025】数的划分(dfs 或 dp 或 母函数,第二类斯特林数Stirling)

版权声明:欢迎学习我的博客,希望ACM的发展越来越好~ https://blog.csdn.net/qq_41289920/article/details/83756822

题干:

题目描述

将整数n分成k份,且每份不能为空,任意两个方案不相同(不考虑顺序)。

例如:n=7,k=3,下面三种分法被认为是相同的。

1,1,5
1,5,1
5,1,1

问有多少种不同的分法。

输入输出格式

输入格式:

n,kn,k (6<n \le 2006<n≤200,2 \le k \le 62≤k≤6)

输出格式:

11个整数,即不同的分法。

输入输出样例

输入样例#1: 复制

7 3

输出样例#1: 复制

4

说明

四种分法为:
1,1,51,1,5;
1,2,41,2,4;
1,3,31,3,3;
2,2,32,2,3.

解题报告:

    这题也是今年十月份左右的时候清理工邀请赛的第二签到题。。当时想不到办法了直接dfs过的。偶然翻洛谷看到了原题、、见到了dp解法和母函数的解法。

   dfs很简单,做个简单的剪枝就好了,这是个看似o(200^6)但是实际上远远没有这么高复杂度的dfs。今天先补上dp的解法,母函数的以后复习的时候再说。

AC代码1:(dfs)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#define ll long long
#define pb push_back
#define pm make_pair
#define fi first
#define se second
using namespace std;
const int MAX = 2e5 + 5;
ll dp[505][505];
int n,k;
void dfs(int last,int sum,int cur) {
	if(cur==k) {
		if(sum==n) cnt++;
		return;
	}
	for(int i=last; sum+i*(k-cur)<=n; i++) {//剪枝,只用枚举到sum+i*(k-cur)<=n为止
		dfs(i,sum+i,cur+1);
	}
}
int main()
{
	cin>>n>>k;
	dfs(1,0,0);
	cout << cnt <<endl;
	return 0 ;
 }

AC代码2:(dp)

dp[i][j]代表把i个小球放到j个盒子中的方法数。

一个简单的想法:以一个数为基准,逐步减少分割数 或 减少待分割数 。(这里选择1)

分为两种情况(因为是无序的所以认为每次都是划分完后所有球从小到大排序了)

a.至少有一个盒子只有一个小球的情况数

b.没有一个盒子只有一个小球的情况数

这样进行划分是因为这种分类可以使a和b都有能写出来的表达式:

a.因为盒子不加区分,那么1的情况数与“将n-1个小球放到k-1个盒子中”的情况数一样

b.没有一个盒子只有一个小球,那么把每个盒子中拿出来一个小球,对应的是“把(n-k)个小球放到k个盒子中的情况数”

这个b情况可以理解为:第一个数不为1时,可以视为先在所有的位置上都加上一个1再对于所有的位置用新的总数求次数,

所以定了的是占有了总数j个,位置仍然是j个,与原来相比没有变化。所以b情况的方案数=把i-j个球放到j个盒子中的方案数。

然后将上面的思路化为动态转移方程:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#define ll long long
#define pb push_back
#define pm make_pair
#define fi first
#define se second
using namespace std;
const int MAX = 2e5 + 5;
ll dp[505][505];
int main()
{
	int n,k;
	for(int i = 1; i<=303; i++) /*dp[i][0] = */dp[i][1] = 1;//那一句到底有没有必要加啊,反正是这个状态用不到?
	dp[1][1] = 1;
	for(int i = 2; i<=303; i++) {
		for(int j = 2; j<=i; j++) {
			if(i-j >= 0) dp[i][j] = dp[i-1][j-1] + dp[i-j][j];//其实要是j<=i这么写的话这一行前面的那个if也可以不要了
			//else dp[i][j] = 0;
		}
	}
	cin>>n>>k;
	cout << dp[n][k]<<endl;
	return 0 ;
 }

总结:

   注意一下dp这里的数组不能开dp[505][25]这样,如果要这么写的话那就加上else也是错的,,因为你想啊i会遍历到300,那j也会跟着到300,所以会越界啊就修改别的数据了,,所以保险起见要么你在内层循环写上j<=min(k,i)或者min(15,i),要么就直接开个打数组来存这些没用的状态就好了嘛反正也是用不到的状态,,恰好也不会卡住空间,,干脆就开这么大好了。

题解https://www.luogu.org/problemnew/solution/P1025

猜你喜欢

转载自blog.csdn.net/qq_41289920/article/details/83756822