[JZOJ2679] 跨时代

题目

题目大意

给你一堆边,你要将它们围成面积最大的矩形。
边不一定要用完,而且围成的矩形不能凸出一块。
n ≤ 16 n\leq 16 n16
l i ≤ 15 l_i \leq 15 li15


思考历程

看到这题的第一眼,就会立马往DP方面去想。
我一开始的想法是求出每种选择的状态可以得到的长度,
然后就可以得出哪些状态可以得到两条长度一样的边。
接下来就开始匹配,找不相交的集合,如果两个都满足条件就统计入答案。
但是我又想,上面的最后一个操作会不会使时间崩掉?
于是我又想了几十分钟,迫不得已去打第三题和第二题了……
打完后回来,仔细想想,发现可以折半搜索!
n n n条边拆成两组,每一条边有 5 5 5种选择,分别为不选和加入 a , b , c , d a,b,c,d a,b,c,d四条边。
5 8 5^8 58的时间枚举左边,然后钦定 a ≤ c a\leq c ac b ≤ d b\leq d bd(如果不满足条件就踢掉,不用交换,因为交换后的情况是能够枚举到的)。我们以 ( c − a , d − b ) (c-a,d-b) (ca,db)作为关键字存在某个表中(用hash或map或数组,怎样都可以)。
之所以存下它们的差,是因为在差相同的情况下,可以互补成完整的矩形。
对于每个出现过的关键字,都给它分配一个数组,存下 ( a , b ) (a,b) (a,b)
然后就是枚举右边的情况,搞完之后以同样的方式在表中找,如果关键字出现过,就在分配给它的数组中枚举,将 ( a i + c ′ ) ( b i + d ′ ) (a_i+c')(b_i+d') (ai+c)(bi+d)统计入答案中。
显然,最花时间的地方就是在数组中枚举,如果这个数组开得很大,有可能会拖慢速度。
我试了一个 16 16 16 8 8 8的数据,就成功卡掉了。
于是我便想到,数组中的重复元素其实是很多的。所以我将vector换成了set,那个数据就跑得贼快了。
去掉了重复,我也不知道数据还有什么办法卡我,因为每个数组都不会特别大(根据数据范围感性地估计一下~)
于是我就交了上去。果然AC,而且还跑得比较快。
后来我又思考,万一数据真的卡我,该怎么办。于是我想到,同一个数组中若有 a i ≤ a j a_i\leq a_j aiaj b i ≤ b j b_i \leq b_j bibj,显然 ( a i , b i ) (a_i,b_i) (ai,bi)是可以不要的。那么在左边的枚举做完之后,我们将每个数组里的元素排序一遍,把其中这些没有意义的元素删去。于是我们就可以得到一个 a i a_i ai递增, b i b_i bi递减的一个东西。显然我们要让 a i d ′ + b i c ′ a_id'+b_ic' aid+bic最大,就可以在数组上二分(或三分?)来做。反正时间复杂度是绝对可以保证的。


正解

其实这道题的方法有很多很多……折半搜索只是冰山一角。
事实上我一开始想的、但却没有打的方法是对的。
有的人直接搜索就过了,有的人背包、状压……
所以就不一一说明了……
于是“正解”这一栏有什么意义?


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>
int n,m;
int len[17];
#define mo 1000007
struct Node{
	int key,num;	
} h[mo];
inline int my_hash(int ia1,int ia2){
	int key=ia1*997+ia2+mo,i=key%mo;
	for (;h[i].key!=key && h[i].key;i=(i+1==mo?0:i+1));
	return i;
}
int cnt;
set<pair<int,int> > arr[15000];
void dfs1(int k,int a,int b,int c,int d){
	if (k>m){
		if (a>b || c>d)
			return;
		int w=my_hash(b-a,d-c);
		if (!h[w].num)
			h[w].num=++cnt;
		arr[h[w].num].insert(make_pair(a,c));
		return;
	}
	dfs1(k+1,a,b,c,d);
	dfs1(k+1,a+len[k],b,c,d);
	dfs1(k+1,a,b+len[k],c,d);
	dfs1(k+1,a,b,c+len[k],d);
	dfs1(k+1,a,b,c,d+len[k]);
}
int ans=0;
void dfs2(int k,int a,int b,int c,int d){
	if (k>n){
		if (a>b || c>d)
			return;
		int w=my_hash(b-a,d-c);
		if (h[w].num){
			for (set<pair<int,int> >::iterator p=arr[h[w].num].begin();p!=arr[h[w].num].end();++p)
				ans=max(ans,(b+p->first)*(d+p->second));
		}
		return;
	}
	dfs2(k+1,a,b,c,d);
	dfs2(k+1,a+len[k],b,c,d);
	dfs2(k+1,a,b+len[k],c,d);
	dfs2(k+1,a,b,c+len[k],d);
	dfs2(k+1,a,b,c,d+len[k]);
}
int main(){
	scanf("%d",&n);
	for (int i=1;i<=n;++i)
		scanf("%d",&len[i]);
	m=n>>1;
	dfs1(1,0,0,0,0);
	dfs2(m+1,0,0,0,0);
	if (ans)
		printf("%d\n",ans);
	else
		printf("No Solution\n");
	return 0;
}

总结

做题的时候不要怂,有时暴力的方法照样能过!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值