P1025 数的划分
题目描述
将整数nn分成kk份,且每份不能为空,任意两个方案不相同(不考虑顺序)。
例如:n=7n=7,k=3k=3,下面三种分法被认为是相同的。
1,1,51,1,5;
1,5,11,5,1;
5,1,15,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.
第一眼就感觉我写过类似的题,但是还是花了我不少的时间,该打。(看算法导论,感觉知道了很多东西,但是还是菜鸡一个.....)
个人想法:又是一个找公式题(但是没感觉啊,分析的超级慢!!!写少了!!)
看题分析:显然 f2(n) = n/2; 只有两个数,一个数确定了另外一个也就确定了。
同时我们知道后面的f(n),都是由前面的f(n)得到的。
比如:
1 1 5
1 2 4
1 3 3
2 2 3
左边的1-2可以看成是多出来的数,f3(7)=f2(7-1)+f2(7-2)(除去重复的》》 减去小于有小于2元素的项。)(希望下次看的时候能看懂  ̄□ ̄||)
f3(n) = f(n-1)+(f(n-2)-重复的//换种角度看就是all元素大于2的数)+(f(n-3)-重复的)...
在f3的时候重复的还可以用f2表示,但是 后面就不要表示了,如果我们保存前面的情况,那么只需要计数就可以了。
AC代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define Max 1005
#define max(a,b) a>b?a:b;
#define min(a,b) a>b?b:a;
/*
写过类似的题,但是推导过程又花了我大把时间
用dp写,推公式
显然 f2(n) = n/2; 只有两个数,一个数确定了另外一个也就确定了
f3(n) = f(n-1)+(f(n-2)-重复的//换种角度看就是all元素大于2的数)+(f(n-3)-重复的)...
在f3的时候重复的还可以用f2表示,但是 后面就不要表示了
如果我们保存前面的情况,那么只需要计数就可以了
、
*/
int f2[Max][210],f3[Max][210],f4[Max][210],f5[Max][210],f6[Max][210];
int n,k;
void fun_f2() //求f2的所有情况
{
for(int i=2;i<=n;i++)
{
f2[i][0]=i/2;
// printf("%d\n",f2[i][0]);
for(int j=1;j<=i/2;j++)
{
f2[i][j]=f2[i][0]-j+1; //记录所有>=i的数的个数,在第0的位置放 f2(n)的值
}
}
}
void fun_f3()//同上
{
int num;
for(int i=3;i<=n;i++)
{
num=0;
for(int j=i/3;j>=1;j--) //从后面计数,数大于i的数
{
num+=f2[i-j][j];
f3[i][j]=num;
}
f3[i][0]=num;
}
}
void fun_f4()
{
int num;
for(int i=4;i<=n;i++)
{
num=0;
for(int j=i/4;j>=1;j--)
{
num+=f3[i-j][j];
f4[i][j]=num;
}
f4[i][0]=num;
}
}
void fun_f5()
{
int num;
for(int i=5;i<=n;i++)
{
num=0;
for(int j=i/5;j>=1;j--)
{
num+=f4[i-j][j];
f5[i][j]=num;
}
f5[i][0]=num;
}
}
void fun_f6()
{
int num;
for(int i=6;i<=n;i++)
{
num=0;
for(int j=i/6;j>=1;j--)
{
num+=f5[i-j][j];
f6[i][j]=num;
}
f6[i][0]=num;
}
}
int main()
{
scanf("%d%d",&n,&k);
/*显然下面的代码写复杂了*/
fun_f2();
if(k==2)
{
printf("%d\n",f2[n][0]);
return 0;
}
fun_f3();
if(k==3)
{
printf("%d\n",f3[n][0]);
return 0;
}
fun_f4();
if(k==4)
{
printf("%d\n",f4[n][0]);
return 0;
}
fun_f5();
if(k==5)
{
printf("%d\n",f5[n][0]);
return 0;
}
fun_f6();
printf("%d\n",f6[n][0]);
return 0;
}
DFS版本:别人的题解+分析 ,代码注释自己看吧。
#include<cstdio>
int n,k,cnt;
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为止
//因为如果后面的位数都等于现在的数(最小情况),他们的和大于了n,那后面的数就没必要计算
dfs(i,sum+i,cur+1); // 和+i,位数+1
}
int main()
{
scanf("%d%d",&n,&k);
dfs(1,0,0); // 从1开始
printf("%d",cnt);
}
比我简单的递归版本!
//别人动归题解
//f[i][x] 表示 i 分成 x 个非空的数的方案数。
//
//显然 i<x 时 f[i][x]=0 , i=x 时 f[i][x]=1;
//
//其余的状态,我们分情况讨论:
//
//①有1的 ②没有1的
//
//第一种情况,方案数为 f[i-1][x-1]
//
//第二种情况,方案数为 f[i-x][x] (此时 i 必须大于 x)
//
//所以,状态转移方程为: f[i][x]=f[i-1][x-1]+f[i-x][x]
#include<bits/stdc++.h>
using namespace std;
int n,k,f[201][7]; //f[k][x] k 分成 x 份 ={f[k-1][x-1],f[k-x][x]}
int main(){
cin >> n >> k;
for (int i=1;i<=n;i++) {f[i][1]=1;f[i][0]=1;}for (int x=2;x<=k;x++) {f[1][x]=0;f[0][x]=0;} // 边界,为了防止炸,我把有0的也处理了
for (int i=2;i<=n;i++)
for (int x=2;x<=k;x++)
if (i>x) f[i][x]=f[i-1][x-1]+f[i-x][x];
else f[i][x]=f[i-1][x-1];
cout<<f[n][k];
return 0;
}
应该多思考动归,经常有思路,但是我的路走的窄,多刷题,多思考加油。
以上三种题解只是用于我个人学习。