P1120 小木棍 [数据加强版]
https://www.luogu.org/blog/user30688/solution-p1120
这题关键在于剪枝。
(1)逆序排列整个序列,从最大的值往回搜
(2)dfs每次从当前点 n o w now now开始搜,而不是每次从最开始的点 a [ 1 ] a[1] a[1]开始搜
(3)在正向的搜索过程中,如果当前积累的长度 n u m num num与平均值 a v e r aver aver的差比整个序列的最小值还小,那么一定不能凑成一组, r e t u r n return return。
(4)技巧部分还在回溯过程:回溯过程中如果找到一组(即 n u m + a [ i ] = = a v e r num+a[i]==aver num+a[i]==aver)或者回到上一层开始的地方(即 n u m = = 0 num==0 num==0),表示当前组不符合, r e t u r n return return。
(5)其次,回溯过程中,如果有很多重复的数字,直接跳过(即 w h i l e ( a [ i ] = = a [ i + 1 ] ) i + + ; while(a[i]==a[i+1]) \quad i++; while(a[i]==a[i+1])i++;)。
#include
#include
#include
using namespace std;
int a[70],vis[70];
int sum=0,maxa=-1,aver=0,cnt=0;
int n,flag=0,m=0;
bool cmp(int x,int y){
return x>y;//逆序
}
void dfs(int step,int num,int now){//从now开始,而不是每次从1开始,节省时间
//step表示搜索的组数,num表示每一组积累的长度,now表示现在正遍历的点
// printf("%d %d\n",step,num);
if(step==cnt||flag){
printf("%d\n",aver),exit(0);//退出
flag=1;
return;
}
if(num==aver){
dfs(step+1,0,1);//从头开始
return;
}
if(aver-num<a[n]) return;//这步是说明在前面的两步判断之后,如果我们的平均值减去这个num的值
//比整个序列的最小值还小,那一定不能再凑成新的一组了。
for(int i=now;i<=n;i++){
if(!vis[i]&& num+a[i]<=aver){
//除了保证i没有被访问过,还要保证num+当前的a[i]小于平均值,才能进入下一步的dfs
vis[i]=1;
dfs(step,num+a[i],i+1);//开始下一步
vis[i]=0; //回溯过程的处理非常关键
if(num+a[i]==aver)
return;//回溯找到一组,不可行
if(num==0)
return;//回溯到上一层最开始的地方,不可行
while(a[i]==a[i+1])
i++;//回溯时消除重复的值 (1)
}
}
}
int main(int argc, char** argv) {
scanf("%d",&n);
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
if(x>50){
continue;
}
a[++m]=x;
if(a[m]>maxa) maxa=a[m];//最大值
sum+=a[m];
}
n=m;
sort(a+1,a+n+1,cmp);//逆序排列,从最大值往回找
for(int i=maxa;i<=sum;i++){
if(sum%i==0){
aver=i,cnt=sum/i;
dfs(0,0,1);
if(flag) break;
}
}
printf("%d\n",aver);
return 0;
}