搜索题目专练(1)

因为觉得自己的搜索还很弱,于是今天就安排了这个搜索专练。。。

今天做的三道题,都是dfs+剪枝。。。做得很纠结啊。。。果然还是很弱啊

第一道题:

[scoi2005]栅栏

题目地址:http://www.lydsy.com/JudgeOnline/problem.php?id=1082

这题直接搜情况太复杂了,所以可以把它变成一个枚举+验证的题

考虑到这样一个贪心的策略:优先满足较小的目标木棍,可以得到较多的目标木棍。

而满足木棍数的可行性也是具有单调性的,也就是说不存在 i<j 使得能够满足j个目标木棍而不能满足i个,因此枚举+验证可以改为二分+验证。

于是我们就可以把目标木棍和拥有的木棍先排个序,然后二分找出最多能满足的木棍数。

现在关键问题就是这个验证怎么解决了,当然要用搜索。

大概就是枚举第1~mid小的每个目标木棍用哪条木棍来割。

接下来的任务就是优化和剪枝了:

一个优化:据说目标木棍按从大到小的顺序枚举会减少很多搜索量(- -我只感觉好像是。。。不知道具体原因)

剪枝1:如果当前目标木棍和上一次考虑的目标木棍的长度相同,那么枚举当前木棍 从哪个大木棍得来 直接从上次用的木棍开始枚举就行了(使用的木棍要从小到大枚举。。。也有点晕)。

剪枝2:如果一根木棍割掉部分后,剩余长度比最小目标木棍还小,那么这一段必然没法再用了,加进一个waste变量(表示废弃木棍的长度)里面,如果总长度<废弃木棍长度+第1~mid小的目标木棍长度之和,剪枝。

#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;
int m,n;
int full[60],full2[60],need[1000+10],sum[1000+10],all=0;
int waste=0;
int mid;
bool dfs(int now,int last)
{
	if(now==0)return true;
	if(waste>all-sum[mid])return false;
	for(int i=last;i<=m;i++)
	{
		if(full2[i]>=need[now])
		{
			full2[i]-=need[now];
			if(full2[i]<need[1])waste+=full2[i];
			int nlast;
			if(need[now-1]==need[now])nlast=i;
			else nlast=1;
			if(dfs(now-1,nlast))return true;
			if(full2[i]<need[1])waste-=full2[i];
			full2[i]+=need[now];
		}
	}
	return false;
}
int main()
{
	scanf("%d",&m);
	for(int i=1;i<=m;i++)scanf("%d",&full[i]),all+=full[i];
	sort(full+1,full+m+1);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&need[i]);
	sort(need+1,need+n+1);
	for(int i=1;i<=n;i++)sum[i]=sum[i-1]+need[i];
	while(need[n]>full[m])n--;
	int l=0,r=n;
	while(l<r)
	{
		mid=(l+r+1)>>1;
		memcpy(full2,full,sizeof(full));
		waste=0;
		if(dfs(mid,1))l=mid;
		else r=mid-1;
	}
	printf("%d\n",l);
	return 0;
}

第二道题:noip 2011 mayan游戏

这道题。。。作为一道noip题,当年可是恶心到了不少的人。

最开始我直接写的最暴力的dfs,写起来的时候并没有感到太恶心。。。调了一会也调出来了。。(不过时间也不短)

这样能够小部分数据。。。于是我一点一点的向里面加优化。。。这个过程的确把我恶心到了,各种T+wa。。。

剪枝有如下几种:

剪枝1:其实把格子和左边的格子交换和 把左边的格子和它交换是同一种操作。。。而左移的字典序要小些,所以除非是左边是空格,不单独考虑左移的情况
剪枝2:两个相同颜色的格子交换没什么用处
剪枝3:如果当前格子和右边格子交换了没有消除或掉落,再交换回来是不用考虑的
剪枝4:如果某种颜色的格子数为1或2,就不可能再消除了,应当立即剪枝
 

不过我这样写出来的代码效率比网上大神的代码效率低了10倍,优化了半天都没效果。。。还好时限是10s勉强可以过,不过我还是想知道这个还能优化什么地方,求各位大神指教可怜

#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int inf=0x3f3f3f3f;
int n;
int map[10][10];//0表示没有方块,数字表示方块颜色。 
int rest;
int restx[15];
struct State
{
	int step;
	int x[10];
	int y[10];
	int op[10];
	bool operator <(const State &b)const
	{
		if(step!=b.step)return step<b.step;
		for(int i=1;i<=step;i++)
		{
			if(x[i]!=b.x[i])return x[i]<b.x[i];
			if(y[i]!=b.y[i])return y[i]<b.y[i];
			if(op[i]!=b.op[i])return op[i]>b.op[i];
		}
		return false;
	}
}ans,st;
bool clear()
{
	if(rest==0)return true;
	return false;
}
void make(int pos)
{
	swap(map[st.x[pos]+st.op[pos]][st.y[pos]],map[st.x[pos]][st.y[pos]]);
}
bool down()
{
	bool ret=false;
	for(int x=0;x<5;x++)
	{
		for(int y=0;y<7;y++)
		{
			if(y>0&&map[x][y]&&!map[x][y-1])
			{
				ret=true;
				int low,up,i;
				for(i=y-1;i>=0&&!map[x][i];i--);
				low=i+1;
				for(i=y;i<7&&map[x][i];i++);
				up=i-1;
				for(i=y;i<=up;i++)
				{
					map[x][i-y+low]=map[x][i];
				}
				for(i=up-y+1+low;i<=up;i++)map[x][i]=0;
			}
		}
	}
	return ret;
}
bool flag[10+10][10+10];
void boom()
{
	memset(flag,0,sizeof(flag));
	for(int x=0;x<5;x++)
	{
		for(int y=0;y<7;y++)
		{
			if(x>0&&x<4)
			{
				if(map[x][y]==map[x-1][y]&&map[x][y]==map[x+1][y])
				{
					flag[x][y]=true;
					flag[x+1][y]=true;
					flag[x-1][y]=true;
				}
			}
			if(y>0&&y<6)
			{
				if(map[x][y]==map[x][y-1]&&map[x][y]==map[x][y+1])
				{
					flag[x][y]=true;
					flag[x][y-1]=true;
					flag[x][y+1]=true;
				}
			}
		}
	}
	for(int x=0;x<5;x++)
	{
		for(int y=0;y<7;y++)
		{
			if(flag[x][y]&&map[x][y]!=0)
			{
				restx[map[x][y]]--;
				map[x][y]=0;
				rest--;
			}
		}
	}
}
bool adjust()
{
	bool ret=false;
	if(down())ret=true;
	boom();
	while(down())//先降下来,不能降就结束了。 
	{	
		ret=true;
		boom();//消除
	}
	return ret;
}
void dfs(bool last)
{
//	if(st.step==3&&st.x[1]==0&&st.y[1]==1&&st.op[1]==1&&st.x[2]==1&&st.y[2]==3&&st.op[2]==1&&st.x[3]==1&&st.y[3]==0&&st.op[3]==1)
//	{
//		printf("hehe");
//	}
	if(clear())
	{
		if(st<ans)ans=st;
	}
	for(int i=1;i<=10;i++)if(restx[i]==1||restx[i]==2)return;
	if(st.step>=ans.step||st.step>=n)return;
	int temp[10][10];
	memcpy(temp,map,sizeof(map));
	int nrest=rest;
	int temprest[15];
	memcpy(temprest,restx,sizeof(restx));
	for(int x=0;x<4;x++)
	{
		for(int y=0;y<7&&(map[x][y]!=0||map[x+1][y]!=0);y++)
		{
			if(last&&x==st.x[st.step]&&y==st.y[st.step])continue;
			if(map[x][y]==0&&map[x+1][y]!=0)
			{
				st.step++;
				st.x[st.step]=x+1;
				st.y[st.step]=y;
				st.op[st.step]=-1;
				memcpy(map,temp,sizeof(map));
				memcpy(restx,temprest,sizeof(restx));
				rest=nrest;
				make(st.step);
				bool nlast=false;
				if(adjust()||rest<nrest)nlast=true;
				dfs(nlast);
				st.step--;
			}
			else if(map[x][y]!=0&&map[x][y]!=map[x+1][y])
			{
				st.step++;
				st.x[st.step]=x;
				st.y[st.step]=y;
				st.op[st.step]=1;
				memcpy(map,temp,sizeof(map));
				memcpy(restx,temprest,sizeof(restx));
				rest=nrest;
				make(st.step);
				bool nlast=false;
				if(adjust()||rest<nrest)nlast=true;
				dfs(nlast);
				st.step--;
			}
			memcpy(map,temp,sizeof(map));
		}
	}
}
void readdata()
{
	scanf("%d",&n);
	for(int i=0;i<5;i++)
	{
		int j=0;
		scanf("%d",&map[i][j]);
		while(map[i][j])
		{
			restx[map[i][j]]++;
			rest++;
			j++;
			scanf("%d",&map[i][j]);
		}
	}
}
int main()
{
	readdata();
	ans.step=inf;
	st.step=0;
	adjust();
	dfs(0);
	if(ans.step==inf)printf("-1\n");
	else
	{
		for(int i=1;i<=ans.step;i++)printf("%d %d %d\n",ans.x[i],ans.y[i],ans.op[i]);
	}
	return 0;
}

第三道题:poj1011 木棍

这个题首先要明确的是目标木棍的长度一定是总长度的因子,所以可以想到从小到大枚举可行的目标木棍长度,然后dfs验证是否能拼出。
这里可能回想到二分。。但遗憾的是这个可行长度不具有单调性。。。只有硬枚举了。
搜索过程的剪枝有如下几个:
1.把小木棍排序,从大到小枚举,这样的好处嘛。。。我觉得有一下几点:a.先考虑了较长的木棍,可以及早发现不可行方案,及早剪枝,减少搜索量。b.相同长度的木棍相邻。。。当前取任意一根所得到的结果一定是相同的。。。又可以减少搜索量。。。(具体的好处我不太理得清。。不过根据经验+感觉,从大到小的枚举应该效率更高)
2.对于枚举的目标木棍完整长度,当前的木棍用上不可行,那么这根木棍永远用不上,可直接判断不可行。
3.剩余木棍长度总和加上已拼部分还是拼不出一根目标木棍,剪枝
4.当前木棍加上已拼部分刚好可以拼成一根目标木棍,立即选用这根木棍是最好的决策,只需要判断选用这根木棍是否能完全拼接即可。。容易想到,如果当前情况下所有目标木棍都能够拼接成功,那么一定有一种可行拼接方案是当前选了这根木棍,反之,如果选了这根木棍就没有可行方案,那么其他的方案也不存在。。。(我是数学渣渣,证明我不会。。。)
5.这条剪枝不知道起不起得到作用:如果当前已拼部分还需要拼上的长度小于最小木棍长度,剪枝

总之,这道题虽然已经前前后后做了4次以上。。。做的时候还是这么。。。可见我是有多弱。。还需要提高啊

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
int Len;
int maxlen=0;
int exsum[64+10]; 
int sum=0;
int a[64+10];
int n;
bool used[64+10];
bool dfs(int ok,int rest,int last)
{
	if(ok==sum/Len)return true;
	if(rest==0)
	{
		return dfs(ok+1,Len,n);
	}
	if(last==0)return false;
	if(rest<a[1])return false;
	int lastno=0;
	for(int i=last;i>=1;i--)
	{
		if(rest<a[i])continue;
		if(a[i]==lastno)continue;//这个lastno必须有啊!! 
		if(rest>exsum[i])return false;
		if(used[i])continue;
			used[i]=true;
			if(dfs(ok,rest-a[i],i-1))return true;
			used[i]=false;
		if(rest==Len)return false;
		if(a[i]==rest)return false;
		lastno=a[i];
	}
	return false;
}
int main()
{
	while(scanf("%d",&n)!=EOF)
	{
		if(n==0)return 0;
		memset(used,0,sizeof(used));
		maxlen=0;sum=0;
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			maxlen=max(maxlen,a[i]);
			sum+=a[i];
		}
		sort(a+1,a+n+1);
		for(int i=1;i<=n;i++)exsum[i]=exsum[i-1]+a[i];
		for(Len=maxlen;Len<=sum;Len++)
		{
			if(sum%Len!=0)continue;
			if(dfs(0,Len,n))
			{
				printf("%d\n",Len);
				break;
			}
		}
	}
	return 0;
}

做了一天的搜索也没找到什么感觉,后面必须再安排一些搜索的训练。。。我真的太弱了 大哭

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值