这个题做了我好久。。想了好多奇葩剪枝。
我的想法是从第一行开始一行一行往下填。
一个比较简单的剪枝是处理出当前剩余面积的最小代价,如果加上最小代价都大于等于当前最优解了,那么就直接减掉就好了。它是可以DP出来的,显然
f[i]=minf[i−j2]+1.(j2≤i)
f[0]=0
但是它与实际情况相差实在太大了,所以我们试图估算它的剩余最小代价;考虑当我们将要填下一行的时候我们知道这时的图形是一个正方形然后从上到下挖去一些矩形形成的,所以我们把这些矩形分裂出来,因为它们要填正方形的话是至少需要[矩形的面积/矩形的宽的平方]个正方形的,所以我们加上这个玩意儿。然后再把剩余面积都弄到一起做f函数,这个做起来非常麻烦,还需要用到单调栈,我写了半天。。。
面对合数的局面,如果使用迭代加深的情况,是可以很快的,因为对于合数而言,其最优解需要的正方形是相当少的,也很有规律。
但是问题在于37、41、43、47这4个大素数,这也是本题最大的难点。。我一直不能很好地处理它们。。也一直没有想出更好的剪枝。而如果有迭代加深的话反而不如直接搜。观察到从19到31每个素数之间最多相差1,而31需要15,所以我推测47应该。。应该不会超过20。
然后我弃疗打算让它们跑上几个小时,然后去看标算。。结果我发现37的只花了5min就跑出了与31的情况一样的15个正方形的解,这绝对是最优解了!而题解也是跑了半个小时才跑出来这几块硬骨头的。。
这是我的代码。。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<ctime>
using namespace std;
int f[2505];
int N,M;
bool a[55][55];
int r[55][55],d[55][55];
struct AS{
int len;
int x,y;
}tmp[2505],ans[2505];
struct SS{
int y,high;
}stack[55];
inline void dfs(int x,int y,int sum,int S){
//cout<<"dfs("<<x<<","<<y<<","<<sum<<","<<S<<")\n";
if(sum+f[S]>=M)return;
if(x<N){
int tot=0;
for(int i=1;i<=N;++i)
if(a[x+1][i])i+=r[x+1][i];
else{
while(i<N&&!a[x+1][i])++i;
++tot;
}
if(sum+tot>=M)return;
}
if(x>N){
M=sum;
cout<<sum<<endl;
memcpy(ans,tmp,sizeof(AS)*M);
for(int i=0;i<M;++i)printf("%d %d %d\n",ans[i].x,ans[i].y,ans[i].len);
return;
}
else if(y>N){
int i,top=0,tot=0,mid,s=0,tmp;
++x;
if(x<=N){
//cout<<"--------"<<x<<"-------\n";
for(i=1;i<=N;++i)
if(!a[x][i]){
while(i<N&&!a[x][i+1])++i;
stack[top++]=(SS){i,0};
}
else{
if(top&&stack[top-1].high<=d[x][i]){
mid=stack[--top].high;
if(top)
while(top&&stack[top-1].high<=d[x][i]){
--top;
if(i-stack[top].y-1<=stack[top].high-mid)tmp=(stack[top].high-mid)/(i-stack[top].y-1);
else tmp=0;
tot+=tmp;
s+=(stack[top].high-mid)*(i-stack[top].y-1)-tmp*(i-stack[top].y-1)*(i-stack[top].y-1);
mid=stack[top].high;
}
else if(d[x][i]!=mid){
if(i-1<=d[x][i]-mid)tmp=(d[x][i]-mid)/(i-1);
else tmp=0;
tot+=tmp;
s+=(d[x][i]-mid)*(i-1)-tmp*(i-1)*(i-1);
}
}
stack[top++]=(SS){i+r[x][i]-1,d[x][i]};
i+=r[x][i]-1;
}
//puts("");
mid=stack[top-1].high;
//printf("top:%d mid:%d tot:%d\n",top,mid,tot);
if(top){
while(--top)s+=(stack[top].y-stack[top-1].y)*(N-(x+stack[top].high-1));
s+=stack[top].y*(N-(x+stack[top].high-1));
}
else if(N!=x+mid-1)tot+=N/(N-(x+mid-1));
//cout<<sum<<" "<<tot<<"+"<<f[s]<<"("<<s<<")"<<endl;
if(sum+tot+f[s]>=M)return;
}
dfs(x,1,sum,S);
}
else
if(a[x][y])dfs(x,y+r[x][y],sum,S);
else{
int i=y;
while(i-y+2<N&&i<N&&i<N-x+y&&!a[x][i+1])++i;
int j,len=i-y+1;
for(i=0;i<len;++i)
for(j=0;j<len;++j)
a[x+i][y+j]=1;
for(;len;--len){
r[x+len][y]=0;
for(i=0;i<len;++i)r[x+i][y]=len;
for(i=0;i<=len;++i)d[x+i][y]=len-i;
tmp[sum]=(AS){len,x,y};
//if(len==1)cout<<"("<<x<<","<<y<<")"<<a[x][y]<<endl;
dfs(x,y+len,sum+1,S-len*len);
for(i=0;i<len;++i)a[x+len-1][y+i]=0;
for(i=0;i<len;++i)a[x+i][y+len-1]=0;
}
}
}
int main(){
//freopen("square.in","r",stdin);
memset(f,127,sizeof(f));
f[0]=0;
for(int i=1;i<=2500;++i){
for(int j=1;j*j<=i;++j)f[i]=min(f[i],f[i-j*j]);
++f[i];
}
N=31;
{
M=20;
memset(a,0,sizeof(a));
dfs(1,1,0,N*N);
printf("%d\n",N);
//printf(out,"%d ",N);
printf("%d\n",M);
for(int i=0;i<M;++i)printf("%d %d %d\n",ans[i].x,ans[i].y,ans[i].len);
}
}
所以,
①对于这种暴搜题,数据规模有限,那考试的时候大可让自己的代码跑去啊!
②对于题目条件相当苛刻的题,一定要相信自己暴搜!