《GMOJ-Senior-2679 跨时代》题解

题目大意

给出 n n n根栏杆的长度,让你从这 n n n根栏杆中选出一些栏杆围成一个矩形(必须要刚好围成一个矩形,即不能出现多余的边长,且不能切断栏杆,但所给栏杆不一定要全部用上),求围成矩形的最大面积。
对于 30 % 30\% 30%的数据, 1 ≤ n ≤ 10 1 \leq n \leq 10 1n10
对于 100 % 100\% 100%的数据, 1 ≤ n ≤ 16 1 \leq n \leq 16 1n16, 1 ≤ 栏杆的长度 ≤ 15 1 \leq \text{栏杆的长度} \leq 15 1栏杆的长度15

分析

这是一道状态压缩动态规划(状压 D P DP DP)题。

30分

对于 30 % 30\% 30%的数据,我们可以用暴力求出。设围成的矩形的四条边分别为 e 1 , e 2 , e 3 , e 4 e_1,e_2,e_3,e_4 e1,e2,e3,e4,那么在 D F S DFS DFS时枚举每条边的每种情况(加入 e 1 e_1 e1 e 2 e_2 e2 e 3 e_3 e3 e 4 e_4 e4),最后判断矩形的长宽是否相等,如果相等就更新答案。代码如下:

#include<cstdio>
int max(int x,int y)//最大值函数
{
	if(x>y)
	{
		return x;
	}
	return y;
}
int n,a[22];
int dfs(int s/*枚举的栏杆的编号*/,int e1/*矩形的第一条长*/,int e2/*矩形的第二条长*/,int e3/*矩形的第一条宽*/,int e4/*矩形的第二条宽*/)
{
	if(s==n+1)//栏杆枚举完了
	{
		if(e1==e2&&e3==e4)//判断矩形是否合法(长、宽是否相等)
		{
			return e1*e3;
		}
		return 0;
	}
	return max(max(max(dfs(s+1,e1+a[s],e2,e3,e4),//加入e1
					   dfs(s+1,e1,e2+a[s],e3,e4)),//加入e2
				   max(dfs(s+1,e1,e2,e3+a[s],e4),//加入e3
					   dfs(s+1,e1,e2,e3,e4+a[s]))),//加入e4
					   dfs(s+1,e1,e2,e3,e4));//不选
}
int main()
{
	scanf("%d",&n);//读入n
	for(int i=1;i<=n;i++)//读入栏杆长度
	{
		scanf("%d",&a[i]);
	}
	int t=dfs(1,0,0,0,0);//DFS
	if(t==0)//如果无解
	{
		printf("No Solution");
	}
	else
	{
		printf("%d",t);//输出答案
	}
	return 0;
}

这样的时间复杂度为 O ( 5 n ) O(5^n) O(5n) 100 % 100\% 100%的数据肯定过不了,于是我们考虑对其优化。

100分

因为我们知道矩形的长、宽相等,所以我们设它的长为 a a a,宽为 b b b,深搜时每根栏杆的情况就由 5 5 5种降到了 3 3 3种:加入 a a a,加入 b b b,不选。最后我们再判断 a a a b b b是否可以分成相等的两部分,如果可以更新答案即可。
如何判断 a a a b b b是否可以分成相等的两部分呢?我们用 f f f数组来判断。 f i f_i fi i i i为一个 01 01 01串)表示对于 i i i这种选择栏杆的情况中,选出的栏杆能否分成相等的两段。因为 i i i是一个 01 01 01串,所以我们可以把它压缩成一个十进制数储存。
对于每个 i i i,我们先求出选出的栏杆的长度总和 s u m sum sum。如果 s u m sum sum是奇数,那么显然 f i = f a l s e f_i=false fi=false。否则我们用 01 01 01不带权背包 g g g辅助处理。我们把选出的栏杆逐个放入 g g g中,然后判断 g s u m ÷ 2 g_{sum \div 2} gsum÷2(一半)能否被组合,若能,表示 s u m − s u m ÷ 2 sum-sum \div 2 sumsum÷2(即另一半)也能被组合, f i = t r u e f_i=true fi=true;否则, f i = f a l s e f_i=false fi=false
根据以上思路,我们可以打出如下代码:

#include<cstdio>
bool f[1<<16],g[242];int n,l[22];
int max(int x,int y)//最大值函数
{
	if(x>y)
	{
		return x;
	}
	return y;
}
int dfs(int s/*枚举的栏杆的编号*/,int a/*长的选择情况*/,int suma/*两条长的总和*/,int b/*宽的选择情况*/,int sumb/*两条宽的总和*/)
{
	if(s==n+1)//栏杆枚举完了
	{
		if(f[a]&&f[b])//判断矩形是否合法(长、宽能否被分成相等的两部分)
		{
			return (suma/2)*(sumb/2);//注意这是两条长、宽的总和,乘的时候记得分别除以2
		}
		return 0;
	}
	return max(max(dfs(s+1,a+(1<<(s-1)),suma+l[s],b,sumb)/*加入长*/,dfs(s+1,a,suma,b+(1<<(s-1)),sumb+l[s]))/*加入宽*/,dfs(s+1,a,suma,b,sumb)/*不选*/);
}
int main()
{
	scanf("%d",&n);//读入n
	for(int i=1;i<=n;i++)//读入读入栏杆长度
	{
		scanf("%d",&l[i]);
	}
	int m=1<<n//求出栏杆选择的情况总数
	for(int i=0;i<m;i++)//枚举每种情况
	{
		int sum=0;//求出选出的栏杆长度总和
		for(int j=1;j<=n;j++)//枚举每一条边
		{
			if((i&(1<<(j-1)))!=0)//判断是否被选中
			{
				sum+=l[j];//累加长度
			}
		}
		if(sum%2!=0)//如果长度除以2不是整数(无法平均分成相等的两个整数部分)
		{
			f[i]=0;
		}
		else
		{
			g[0]=1;//初始化01不带权背包
			for(int j=1;j<=sum;j++)
			{
				g[j]=0;
			}
			for(int j=1;j<=n;j++)//枚举每一条边
			{
				if((i&(1<<(j-1)))!=0)//判断是否被选中
				{
					for(int k=sum;k>=l[j];k--)//放入背包
					{
						g[k]|=g[k-l[j]];
					}
				}
			}
			f[i]=g[sum/2];//判断能否被分成相等的两部分
		}
	}
	int t=dfs(1,0,0,0,0);//DFS
	if(t==0)//如果无解
	{
		printf("No Solution");
	}
	else
	{
		printf("%d",t);//输出答案
	}
	return 0;
}

总结

在做像这样的状压 D P DP DP题时,可以先打出暴力,然后再尝试对其优化。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值