P1120 暴搜+剪枝

不是题解!不是题解!不是题解!重要的事情说三遍!纯属个人学习笔记!

传送门:P1120
AC代码:

#include<bits/stdc++.h>
using namespace std;
int s[100],vis[100],next1[100];
bool ok;
int number,len;
int sum;
inline int read()
{
	int f=1,w=0;
	char ch=getchar();
	while(ch>'9'||ch<'0')
	{
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		w=w*10+ch-'0';
		ch=getchar();
	}
	return f*w;
 } 
bool cmp(int a,int b)
{return a>b;}
void dfs(int k,int last,int rest)
{
	int i;
	if(!rest)
	{
		if(k==number)
		{ok=1;return;}
		for( i=1;i<=sum;i++)
		{
			if(!vis[i])break;
		}
		vis[i]=1;
		dfs(k+1,i,len-s[i]);
		vis[i]=0;
		if(ok)return ;
	}
	int l=last+1,r=sum,mid;
	while(l<r)
	{
		mid=(l+r)>>1;
		if(s[mid]<=rest)r=mid-1;
		else l=mid+1;
	}
	//cout<<rest<<' '<<t<<endl;
	for(int i=l;i<=sum;i++)
	{
		if(!vis[i])
		{
			vis[i]=1;
			dfs(k,i,rest-s[i]);
			vis[i]=0;
			if(ok)return ;
			if(rest==s[i]||rest==len)return ;
			i=next1[i];
			if(i==sum)return ;
		}
	}
	
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n;
	n=read();
	//cout<<n<<endl;
	sum=0;
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		int t;t=read();
		if(t>50)continue;
		s[++sum]=t;
		ans+=t;
	}
	sort(s+1,s+1+sum,cmp);
	next1[sum]=sum;
	for(int i=sum-1;i;i--)
	{
		if(s[i]==s[i+1])next1[i]=next1[i+1];
		else next1[i]=i;
	}
	for(len=s[1];len<=ans/2;len++)
	{
		if(ans%len!=0)continue;
	    number=ans/len;
		ok=0;
		vis[1]=1;
		dfs(1,1,len-s[1]);
		vis[1]=0;
		if(ok)
		{cout<<len<<endl;return 0;}
	}
	cout<<ans<<endl;
	return 0;
 } 

写搜索对于我来说是一件很痛苦的事情,因为都知道这是搜索,但如果剪枝(优化)剪不好就是无限被T。并且这类搜索题如果出现一些神仙优化,剪枝就是脑子爆炸。

所以这里就拿这道题来聊聊思路。


1.如果直接暴力搜索该怎么写?
不难分析出原始木块的长度一定处于 max(s[i])sum 之间,所以我们可以用原始长度为操作点,分析每一种长度是否满足情况,如果出现满足条件的长度,就直接输出,因为可以保证输出的是最小值。

确定了原始长度len之后,就要分析,哪些木棍组合可以组合成原始长度为len 的木棍 。
这里最暴力可以想到搜索,找出所有可能的情况,并且分析是否可满足初始条件。


2,确定了最暴力的解法之后,优化成了最大boss。

  1. 出现的木棍长度可能有重复,如果已知 len (i) 不满足条件,下一次搜索的应该是与len(i) 不同的数,所以可以建立一个next数组,避免重复。
    next数组的建立也很有意思,在上次模拟赛中有一个签到题就要用到next数组进行优化,但那时候我还不会,就卡了很久。
    这里给出的代码可以当成板子来记:
	for(int i=sum-1;i;i--)
	{
		if(s[i]==s[i+1])next1[i]=next1[i+1];
		else next1[i]=i;
	}
  1. 在遍历搜索的时候,不难想到,每次搜索都要搜索比现在位置后的数,不用搜索比现在位置还前的数字,因为如果下一个数字出现在当前位置之前就一定在之前的搜索中查询过。所以每次向后找就行了,
  2. 在找下一个木棍的时候,利用二分的方法可以快速查找到可行的位置,如果不用二分会被T掉一个点。
  3. 本题最神仙的是if(rest==s[i]||rest==len)return ; 这个点我绝对想不到。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值