「一本通 1.3 例 3」小木棍

小木棍

题目描述

原题来自:\(CERC 1995\)

乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过 \(50\) 。现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度

思路

​ 首先这是一个非常适合刚学剪枝的同学练习,真的有点小全

​ 题目给的信息里有多个自变量,因此我们就可以想到用深搜进行暴力枚举,不过会 \(TLE\)

基本暴力思路就是先枚举答案,保证是在当前答案下可以刚好凑成,在此情况下进行深搜就可以了,找的第一个答案就是最小的。

  • 对于答案的枚举

    \(Maxx\) 找到现在木棍中最长的

    \(Sum\) 表示所有木棍的总长度

    这样我们答案一定就在 \(Maxx -sum\) 对不对

    for( len = maxx;len <= sum; len++){
    		if(sum % len != 0) continue;
        /*
        	整除才可以作为答案的一种,因为原木棍是整数
        */
    		cnt = sum / len;//cnt 原木棍的数量
    		memset(vis,0,sizeof(vis));
    		if(dfs(1,0,1)){
    			cout << len <<endl;
    			break;
    		} 
    	}
    
  • 深搜 \(dfs( s, js, last)\)

    \(s\) 即正在拼第 \(s\)根木棒(确保前面的都拼好了)
    \(s\) 根木棒的当前长度为 \(js\)
    拼第 \(s\) 根木棒的上一根小木棒为 \(last\)

    (有些小朋友可能会有疑问,为什么第一次搜索是 \(dfs(1,0,1)\) 而不是 \(dfs(1,0,0)\) 首先,如果 \(dfs(1,0,0)\) 也能过并且更正确一些,而即使 \(dfs(1,0,1)\)\(dfs\) 中的第三个if中,因为 v[i]==1 也不会进入)

  • 剪枝

    1. 对于每根木棒,\(fail\) 记录的是最近一次尝试拼接的木棍长度。这样再回溯时就不会再尝试相同长度的木棍。
    2. 限制先后加入一根原始木棍的长度是递减的。因为先拼上一个长为 \(x\) 的木棍再拼上一个长为 \(y\) 的木棍,等效于先拼上一个长为 \(y\) 的木棍再拼上一个长为 \(x\) 的木棍。所以只需要搜索其中一种即可。
    3. 如果在一根原始木棒中尝试拼接的第一根木棍的递归分支就以失败返回,直接判断当前分支无解。
    4. 如果两个木棍的长度和与一个木棍的一样,只尝试一个的就行了(因为前两个可能会有更大的效用
    5. 优化搜索顺序,优先尝试较长的木棍。(这就是排序的原因)

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <string.h>
using namespace std;
typedef long long ll;

const int manx=1e6+10;
const int mamx = 1e6 + 11;
const ll mod = 2123400401301379571;
const int inf = 0x3f3f3f3f;

inline int read() {
  char c = getchar(); int x = 0, f = 1;
  for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
  for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
  return x * f;
}
int n,a[manx],maxx,sum,cnt,len;
int cmp(int a,int b){
	return a > b;
} 
int vis[manx];
int dfs(int s,int js,int last){
	if(s > cnt) return true;
	if(js == len) return dfs(s+1,0,1);
	int fail = 0;
	for(int i = last;i <= n; i++){//第二剪枝 
		if(vis[i] == 0 && a[i] + js <= len && fail != a[i]){//第一剪枝 
			vis[i] = 1;
			if(dfs(s,js+a[i],i)) return true;
			vis[i] = 0;//还原搜索前的状态。
			fail = a[i];//回溯状态,防止i之后的数例在会出现相同的数,剪枝~
			if(js == 0 || js + a[i] == len) return false;//第三第四剪枝 
		}
	}
	return false;
}
int main(){
	n = read();
	for(int i = 1;i <= n ; i++){
		a[i] = read();
		sum += a[i]; 
		maxx = max(maxx,a[i]);
	}
	sort(a+1,a+1+n,cmp);
	for( len = maxx;len <= sum; len++){
		
		if(sum % len != 0) continue;//第五剪枝 
		cnt = sum / len;
		memset(vis,0,sizeof(vis));
		if(dfs(1,0,1)){
			cout << len <<endl;
			break;
		} 
	}
	return 0;
}


posted @ 2020-12-20 17:47  zxsoul  阅读(96)  评论(0编辑  收藏  举报