题目大意
给出
n
n
n根栏杆的长度,让你从这
n
n
n根栏杆中选出一些栏杆围成一个矩形(必须要刚好围成一个矩形,即不能出现多余的边长,且不能切断栏杆,但所给栏杆不一定要全部用上),求围成矩形的最大面积。
对于
30
%
30\%
30%的数据,
1
≤
n
≤
10
1 \leq n \leq 10
1≤n≤10;
对于
100
%
100\%
100%的数据,
1
≤
n
≤
16
1 \leq n \leq 16
1≤n≤16,
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
sum−sum÷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题时,可以先打出暴力,然后再尝试对其优化。