落谷P1025解析

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;
} 

应该多思考动归,经常有思路,但是我的路走的窄,多刷题,多思考加油。

以上三种题解只是用于我个人学习。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值