【普及/提高−】P1025 ——[NOIP 2001 提高组] 数的划分

发布于:2025-06-12 ⋅ 阅读:(23) ⋅ 点赞:(0)

见:P1025 [NOIP 2001 提高组] 数的划分 - 洛谷

题目描述

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

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

1,1,5;
1,5,1;
5,1,1.

问有多少种不同的分法。

输入格式

n,k (6<n≤200,2≤k≤6)

输出格式

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

输入输出样例

in:
7 3

out:
4

说明/提示

四种分法为:
1,1,5;
1,2,4;
1,3,3;
2,2,3.

【题目来源】

NOIP 2001 提高组第二题

这道题如果思路想明白了,剩下的很简单!
拿到题目一看就是深度优先搜索的剪枝技巧。

首先普及一下这种算法:

因为搜索算法的时间复杂度大多都是指数级的,

所以在比赛或者考试中很少用到,

即使是简单的搜索,

时间复杂度也令人无法忍受,

所以就要加入一些优化技巧,

比如深搜的剪枝技巧

一、剪枝策略的寻找的方法

1)微观方法:从问题本身出发,发现剪枝条件。

2)宏观方法:从整体出发,发现剪枝条件。

3)注意提高效率,这是关键,最重要的。  

总之,

剪枝策略,

属于算法优化范畴;

通常应用在DFS 和 BFS 搜索算法中;

剪枝策略就是寻找过滤条件,

提前减少不必要的搜索路径。

二、剪枝优化三原则

正确、准确、高效。

原则 :搜索算法,

绝大部分需要用到剪枝。

然而,

不是所有的枝条都可以剪掉,

这就需要通过设计出合理的判断方法,

以决定某一分支的取舍。

在设计判断方法的时候,

需要遵循一定的原则。

 1) 正确性 :

正如上文所述,

枝条不是爱剪就能剪的。

如果随便剪枝,

把带有最优解的那一分支也剪掉了的话,

剪枝也就失去了意义.。

所以,

剪枝的前提是一定要保证不丢失正确的结果。

2)准确性 :

在保证了正确性的基础上,

我们应该根据具体问题具体分析,

采用合适的判断手段,

使不包含最优解的枝条尽可能多的被剪去,

以达到程序“最优化”的目的。

可以说,剪枝的准确性,

是衡量一个优化算法好坏的标准。

3)高效性 :

设计优化程序的根本目的,

是要减少搜索的次数,

使程序运行的时间减少。

但为了使搜索次数尽可能的减少,

我们又必须花工夫设计出一个准确性较高的优化算法,

而当算法的准确性升高,其判断的次数必定增多,

从而又导致耗时的增多,这便引出了矛盾。

因此,如何在优化与效率之间寻找一个平衡点,

使得程序的时间复杂度尽可能降低,同样是非常重要的。

倘若一个剪枝的判断效果非常好,

但是它却需要耗费大量的时间来判断、比较,

结果整个程序运行起来也跟没有优化过的没什么区别,这样就太得不偿失了。

三、深度优先搜索的优化技巧

1 优化搜索顺序:

在一些搜索问题中,

搜索树的各个层次,

各个分支之间的顺序是不固定的。

不同的搜索顺序会产生不同的搜索树形态,

其规模大小也相差甚远。

2 排除等效冗余:

在搜索过程中,

如果我们能够判定从搜索树的当前节点上沿着某几条不同分支到达的子树是等效的,

那么只需要对其中的一条分支执行搜索。

3 可行性剪枝(上下界剪枝):

该方法判断继续搜索能否得出答案,

如果不能直接回溯。

在搜索过程中,

即使对当前状态进行检查,

如果发现分支已经无法到达递归边界,

就执行回溯。

4 最优性剪枝:

最优性剪枝,

是一种重要的搜索剪枝策略。

它记录当前得到的最优值,

如果当前结点已经无法产生比当前最优解更优的解时,

可以提前回溯。

5 记忆化:

可以记录每个状态的搜索结果,

再重复遍历一个状态时直接检索并返回。

这好比我们对图进行深度优先遍历时,

标记一个节点是否已经被访问过。

关于本题

就如前面所说,

想要进行深搜剪枝,

就要确定剪枝的上下界,

想要确定上下界,

一般会根据约束条件。

本体的约束条件:

由于分解出来的数字不考虑顺序,

所以我们假设分解的数字依次递增,

所以扩展节点时不能小于前一个扩展节点的值,

即a[i-1]<=a[i],这就是“下界”

假设我们已经将N分解成了a[1]加到a[i-1],

则下一个数的最大值是将i-k这k-i+1个数平均分,

即m/k-i+1,

这就是“上界”。

代码详解:

cin>>n>>m;
a[0]=1;
dfs(1);

这里是输入和深搜的阶段,将分解出来的数组的第一个置为1

if(n==0) return ;

这里是深度搜索的结束条件,也就是数被分完了

​if(k==m){
	if(n>=a[k-1]) s++;
	return ;
}

这里是处理异常情况,分出第K份

​for(int i=a[k-1];i<=n/(m-k+1);i++){
	a[k]=i;
	n-=i;
	dfs(k+1);
	n+=i;
}

这里是根据第K份的上下界来求出第K份的值

code

#include<bits/stdc++.h>
using namespace std;
int n,m,a[8],s=0;
void dfs(int k) {
	if(n==0) return ;
	if(k==m) {
		if(n>=a[k-1]) s++;
		return ;
	}
	for(int i=a[k-1]; i<=n/(m-k+1); i++) {
		a[k]=i;
		n-=i;
		dfs(k+1);
		n+=i;
	}
}
int main() {
	cin>>n>>m;
	a[0]=1;
	dfs(1);
	cout<<s<<endl;
	return 0;
}

听说给点赞,关注,收藏的人会发财哦(灬~ω~灬) 


网站公告

今日签到

点亮在社区的每一天
去签到