洛谷传送门
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过 50 50 50。
现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。
给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
第一行是一个整数 n n n,表示小木棍的个数。
第二行有 n n n 个整数,表示各个木棍的长度 a i a_i ai。
输出一行一个整数表示答案。
9
5 2 1 5 2 1 5 2 1
6
对于全部测试点, 1 ≤ n ≤ 65 , 1 ≤ a i ≤ 50 1 \leq n \leq 65,1 \leq a_i \leq 50 1≤n≤65,1≤ai≤50。
很显然,这是一道搜索题。
但显然,暴搜是过不去的,所以我们要考虑剪枝。
相信很多小盆友知道这一点,但一不小心就会写错(把根剪掉了),所以,我们需要对照着代码一步一步的分析:
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", w + i), tot += w[i];
tot
,方便以后做剪枝。sort(w + 1, w + n + 1, greater<int>());
for (int i = w[1]; i <= tot / 2; i++)
if (tot % i == 0)
dfs(1, 0, i, tot / i);
i
。显然,i
的最小值必然就为所有木棒的长度的最大值 w[1]
(因为已经排过序了),而最大值应该只会到 tot / 2
,因为原来长度必然是总长度的一个因数,而 tot / 2 + 1
~ tot - 1
之间不存在 tot
的因子。void dfs(int pre, int sum, int LEN, int target)
pre
用于重复性剪枝,代表上一次所选的木棒的下标 +1。因为我们是按从大到小选的,这一次选的木棒应该是小于等于上一次选的,当前只需要从 pre
开始搜即可。sum
表示的是当前正在拼的木棒拼了多长。LEN
是个常量,也就是木棒的原来长度。target
表示还需要拼出多少根木棒。if (!target)
{
printf("%d", LEN);
exit(0);
}
if (sum == LEN)
{
dfs(1, 0, LEN, target - 1);
return;
}
target
为 0 0 0,也就是所有的木棒拼完了,由于我们的 LEN
是从小到大枚举的,直接输出 exit(0);
即可。if (LEN - sum < w[n])
return;
w[n]
(可以理解为所有木棒长度的最小值)还要小,那么必然不可能拼出 LEN
长度的木棍。还需要注意,这段可行性剪枝应该放在上一段的下面。for (int i = pre; i <= n; i++)
if (!vis[i] && sum + w[i] <= LEN)
{
vis[i] = true;
dfs(i + 1, sum + w[i], LEN, target);
vis[i] = false;
if (!sum || sum + w[i] == LENi)
break;
while (w[i] == w[i + 1])
i++;
}
pre
~ n
。vis
数组标记该木棒是否被选过,如果未被选过并且当前拼的长度+这根木棍的长度不超过总长 LEN
,那么我们拼上这根木棍并进入下一层 dfs 去尝试。vis
标记。接着,如果当前 sum
为 0 0 0 或者 sum + w[i]
为 LEN
时直接跳出循环,为什么呢?
sum
为 0
时,代表这是拼木棍的第一根棒子,并且后面无论怎么拼都不行,那么当前这一根换成其他的当然也就不行。sum + w[i]
为 LEN
时,意味着这是拼木棍的最后一根棒子,而后面怎么拼也都不行,那换成其他的棒子来拼也依旧不行。接着,由于长度为 w[i]
的棒子拼上去不行,与它长度相等的拼上去也不行,所以 i
需要继续往后跳。
一般情况下,如果你将上面所有的代码结合起来,这道题就应该可以 A 掉了,但在洛谷上,却只有 87 分。因此还要在添加一个小小的优化:
w[i]
不行就找下一个与 w[i]
长度不相等的棍子来拼,我们仍需要将 i
一点一点的挪动,为何不能先预处理出每一根棍子后第一根与它长度不相等的棍子的下标呢?for (int i = 1; i <= n; i++)
{
int pos = i + 1;
while (w[pos] == w[i] && pos <= n)
pos++;
nxt[i] = pos;
}
while
语句改成 i = nxt[i] - 1
(由于 i
本身还会往后挪所以要 -1),就可以勉强通过了。#include
using namespace std;
#define INF 0x3f3f3f3f
int n, tot, maxV, minV = INF, w[65], nxt[65];
bool vis[65];
void dfs(int pre, int sum, int LEN, int target)
{
if (!target)
{
printf("%d", LEN);
exit(0);
}
if (sum == LEN)
{
dfs(1, 0, LEN, target - 1);
return;
}
if (LEN - sum < w[n])
return;
for (int i = pre; i <= n; i++)
if (!vis[i] && sum + w[i] <= LEN)
{
vis[i] = true;
dfs(i + 1, sum + w[i], LEN, target);
vis[i] = false;
if (sum + w[i] == LEN || !sum)
break;
i = nxt[i] - 1;
}
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", w + i), tot += w[i];
sort(w + 1, w + n + 1, greater<int>());
for (int i = 1; i <= n; i++)
{
int pos = i + 1;
while (w[pos] == w[i] && pos <= n)
pos++;
nxt[i] = pos;
}
for (int i = w[1]; i <= tot / 2; i++)
if (tot % i == 0)
dfs(1, 0, i, tot / i);
printf("%d", tot); // 记得最后如果所有情况都不行的话要输出总长
return 0;
}
好了,那么这篇博客就到这里了。如果觉得写的好的话,还可以点赞+收藏哦 ^ ⌣ ^ \hat{}\smile\hat{} ^⌣^