P1120 小木棍 [数据加强版] 题解

原题链接

简要题意:

把若干 \(\leq 50\) 的小木棍拼成若干长度相同的长木棍(一个小木棍也可以作为一根长木棍)。求可以拼成的长木棍的最小长度。

暴力出奇迹

一看数据范围,\(n \leq 65\).

这一看就是指数级复杂度 我还没见过什么 \(O(n^5)\) 的算法。。

首先考虑 \(\texttt{dfs}\),枚举长木棍的长度,然后用 \(\texttt{dfs}\) 进行暴力枚举当前的小木棍分给哪一组(可以算出组数的),验证即可。

对这种最 卑劣 不太行的搜索,下面开始大力剪枝:

  1. 假设木棍总长度为 \(s\),则长木棍的长度 \(x\) 应满足 \(x | s\),否则分不成整数组。所以不是 \(s\) 因数的情况可以直接跳过。如果小于小木棍的最长长度也可以跳过,因为最长的木棍没法拼了。
  2. 如果你现在需要长度 \(5\) 的木棍拼成一个长木棍,你会选 \(5\) 一根 还是 \(2 + 3\) 两根呢?

显然选 \(5\). 因为小的木棍永远比大木棍灵活,大木棍能拼的小木棍也能,但小木棍能拼的大木棍未必。

所以将木棍长度最大到小排序,优先选大的。

  1. 如果当前木棍拼接失败,不再尝试相同长度的木棍,直接跳到后面一个不同的。为什么呢?假设给定数据:
10
6 6 6 3 3 3 3 3 3 3

总和为 \(30\),你在验证 \(10\).

你拼上一个 \(6\),又拼上一个 \(3\),不行;然后你就把后面的 \(3\) 一个一个全都尝试一遍发现不行。

然后你又试第 \(2\) 个 \(6\),又是 \(7\) 个 \(3\) 枚举一遍,然后第 \(3\) 个。

但是,状态的大量重复 导致根本没有必要搜这么多次。

我当前长度都失败,换个相同长度的不也是失败?

所以,用 \(\texttt{nxt_i}\) 表示与 \(a_i\) 不同的(排序后)编号 \(\geq i\) 的最小编号(\(\texttt{nxt_n=n}\)),失败后直接往 \(\texttt{nxt}\) 上面跳就可以了。

  1. 假设当前离一个长木棍的长度相差 \(\text{no}\),那么直寻找 \(\leq no\) 的。

你可能说这循环扫一遍?但是经过排序的数组明明可以二分

所以,又优化了一点。(据我所知,这个优化不写应该也可以 \(\text{AC}\) 吧,但是写了比较好)

  1. 每次木棍是否用过的标记数组,不需要每次 \(\texttt{memset}\),可以在搜索中:选当前木棍则先标记,然后进入下一层;下一层结束之后再取消标记,为的是不要影响再一次回溯。这样可以用搜索同级时间维护。(这是一个很常用的技巧)
  2. 如果以及发现,每个木棍都拼了上去,那么不用再回溯了,直接输出答案结束整个程序。(其实也不用一层层的退出,直接结束程序,用 \(\text{exit(0)}\))
  3. 如果当前木棍接上之后正好能拼成一根长木棍,然后经过回溯发现失败了,那么就把之前拼在这个长木棍上的依次废掉,重新拼。为什么呢?

因为,我们是先选长木棍,肯定比短木棍来拼要优(上述已经说明),所以如果长木棍都拼不了,那当前这条长木棍的拼法就是不对的。所以需要改变之前的拼法。

为什么是“依次”呢?假设我们先废掉了一根木棍,然后重新拼;这时如果合法就直接结束了,否则回溯到此不合法。然后就把再之前的木棍废掉,依次类推。

  1. 如果已经成功拼成了(组数-1) 根木棍,由于整除性,剩下的部分肯定能拼成了。所以可以直接判定正确。
  2. 按照管理员大大的暗示 把 \(>50\) 的直接去掉即可。
  3. 可以先从 \(1\) ~ \(\lfloor \frac{s}{2} \rfloor\),如果最终不合法再可以输出 \(s\).(因为 \(s\) 肯定合法)其实可以先枚举因数,但是不会有多少的优化。

有了这些剪枝,我们就可以愉快地 暴力碾标算 啦!

时间复杂度:未知。(难以分析)

实际得分:\(100pts\).(反正对了就行)

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;

inline int read(){char ch=getchar();int f=1;while(ch<‘0‘ || ch>‘9‘) {if(ch==‘-‘) f=-f; ch=getchar();}
	int x=0;while(ch>=‘0‘ && ch<=‘9‘) x=(x<<3)+(x<<1)+ch-‘0‘,ch=getchar();return x*f;}

int n,m,a[66],s=0,len;
int nxt[66]; bool ok,h[66];

inline void dfs(int dep,int last,int no) {
	if(!no) { //需要重新开始拼一根
		if(dep==m) {
			printf("%d\n",len); exit(0); //退出
		} int wz;
		for(int i=1;i<=a[0];i++)
			if(!h[i]) {wz=i;break;}
		h[wz]=1; //第一个没有拼的,就把它拼上去(因为长的比短的优)
		dfs(dep+1,wz,len-a[wz]);
		h[wz]=0; if(ok) {
			printf("%d\n",len); exit(0);
		} //退出
	} int l=last+1,r=a[0];
	while(l<r) {
		int mid=(l+r)>>1;
		if(a[mid]<=no) r=mid;
		else l=mid+1; //二分可以拼的最长木棍
	} for(int i=l;i<=a[0];i++)
		if(!h[i]) {
			h[i]=1; dfs(dep,i,no-a[i]);
			h[i]=0; if(ok) {
				printf("%d\n",len); exit(0); //直接退出
			} if(no==a[i] || no==len) return; //发现拼不了
			i=nxt[i]; if(i==a[0]) return; //说明整根拼不上
		}
}

int main(){
	n=read();
	for(int i=1,t;i<=n;i++) {
		t=read(); //把 >50 的剪掉
		if(t<=50) a[++a[0]]=t,s+=t;
	} sort(a+1,a+1+a[0]); reverse(a+1,a+1+a[0]); //从大到小排序
	nxt[a[0]]=a[0]; for(int i=a[0]-1;i>0;i--)
		nxt[i]=(a[i]==a[i+1])?nxt[i+1]:i; //后面和自己不相同的最小编号
	for(len=a[1];len<=(s>>1);len++) { //位运算再次优化
		if(s%len) continue; //整除
		m=s/len; ok=0; h[1]=1; //标记
		dfs(1,1,len-a[1]); h[1]=0;
	} printf("%d\n",s);
	return 0;
}

原文地址:https://www.cnblogs.com/bifanwen/p/12603970.html

时间: 2024-10-07 05:40:33

P1120 小木棍 [数据加强版] 题解的相关文章

luogu P1120 小木棍 [数据加强版]

二次联通门 : luogu P1120 小木棍 [数据加强版] /* luogu P1120 小木棍 [数据加强版] 暴搜 + 剪枝 枚举可能的长度 挨个检查答案 二分显然正确性不能保障 搜索时从最大的开始找 放上当前木棍后的长度比枚举的长度要大, 则退出 若当前的长度与当前扫到的木棍长度相同, 或是还需要的长度与枚举的长度相同,则退出 若当前的木棍不符合要求, 则后面与它长度相同的木棍都不行 */ #include <algorithm> #include <iostream>

P1120 小木棍 [数据加强版]

题目描述 乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50. 现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度. 给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度. 输入输出格式 输入格式: 输入文件共有二行. 第一行为一个单独的整数N表示砍过以后的小木棍的总数,其中N≤65 (管理员注:要把超过50的长度自觉过滤掉,坑了很多人了!) 第二行为N个用空个隔开的正整数,表示N根小木棍的长度. 输出格式: 输出文件仅一行,表示要求

小木棍 [数据加强版]

题目描述 乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50. 现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度. 给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度. 输入输出格式 输入格式: 输入文件共有二行. 第一行为一个单独的整数N表示砍过以后的小木棍的总数,其中N≤65 (管理员注:要把超过50的长度自觉过滤掉,坑了很多人了!) 第二行为N个用空个隔开的正整数,表示N根小木棍的长度. 输出格式: 输出文件仅一行,表示要求

洛谷P1120小木棍[DFS]

题目描述 乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50. 现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度. 给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度. 输入输出格式 输入格式: 输入文件共有二行. 第一行为一个单独的整数N表示砍过以后的小木棍的总数,其中N≤60 (管理员注:要把超过50的长度自觉过滤掉,坑了很多人了!) 第二行为N个用空个隔开的正整数,表示N根小木棍的长度. 输出格式: 输出文件仅一行,表示要求

洛谷P1120 小木棍

洛谷1120 小木棍 题目描述 乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50.     现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度.     给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度. 输入输出格式 输入格式: 输入文件共有二行. 第一行为一个单独的整数N表示砍过以后的小木棍的总数,其中N≤60 (管理员注:要把超过50的长度自觉过滤掉,坑了很多人了!) 第二行为N个用空个隔开的正整数,表示N根小木棍的长度.

P1120小木棍

题目传送P1120 有许多剪枝的搜索 先考虑答案的范围,先把小木棍排个序,原木棍的长一定大于等于最长的一根 然后,就枚举一下原木棍的长度,搜是否能按这个长度拼成所有的木棍,如果能拼成直接就是答案了 确定了原始长度,然后用总长度就能算出原始木棍的条数.如果不能整除的话,肯定不是答案 然后确定搜索的状态,当前拼到第几跟,这一根还剩多长,拼这一根用的上一根小木棍的编号 我们肯定要先拼大的,再用小的去补大的 如果我们用小的凑的话,那些大的很难凑齐 还有一个优化就是,有很多长度相同的小木棍,对于一种长度不

洛谷 P1120 小木棍

题目描述 乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50. 现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度. 给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度. 输入输出格式 输入格式: 输入文件共有二行. 第一行为一个单独的整数N表示砍过以后的小木棍的总数,其中N≤60 (管理员注:要把超过50的长度自觉过滤掉,坑了很多人了!) 第二行为N个用空个隔开的正整数,表示N根小木棍的长度. 输出格式: 输出文件仅一行,表示要求

小木棍 (codevs 3498)题解

[问题描述] 乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过100. 现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度. 给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度. [样例输入] 9 5 2 1 5 2 1 5 2 1 [样例输出] 6 [解题思路] 这道题的核心思想就一个字,搜……首先找到最大的那一段,原始木棍的可能长度必定>=最大的那一段木棍的长度,所以我们从最大的那一段长度开始往木棍总长度搜,将木棍排序,定义f布尔

[bzoj2594][Wc2006]水管局长数据加强版

论蒟蒻的自我修养T_T.. 和noi2014魔法森林基本一样...然而数据范围大得sxbk...100w你告诉我(n+m)log(n+m)可过?[掀桌] 蒟蒻又蠢了..复杂度应该是O((n+q)log(n+m))吧.. 一开始数组开太小re了两发(要开到maxn+maxm),然后又开太大mle一发,然后无限tle...把记录类型全改成数组还是tle.... 最后把非lct部分改得和黄学长全部一样终于20+s卡过去了......... 然后发现自己原来是有个地方写萎了..一开始把没被删的边做kru