1. P1378 油滴扩展
题意
在长方形框中,最多有 n ($0\le{n}\le6$)个相异点,在框中点上依次放置可扩展的油滴,当碰到其他油滴边界或者长方形边框时会停止,扩展呈圆形展开。放置下一个时会确保上一个已经扩展完成。问通过变换放置顺序可使得最终框中剩下的面积最小为多少。
思路
这是个裸的dfs,情况最多也就 $6! = 720$ 种,所以我们可以只需要设置一个vis数组来记录是否已经放置过这个油滴,计算已扩展油滴和将要放的油滴之间的距离可以用 两点距离-扩展油滴的半径来实现 ,但是有个坑需要注意,就是当一个油滴已经放在已经有扩展油滴覆盖的区域,那么他俩的距离是0,而不是负数,因此在计算半径的时候需要优化一下。
代码实现
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iostream>
#define pi 3.1415926
using namespace std;
const int maxn=10;
int n,x,y,xx,yy;
double rx[maxn];
double maxans;
bool vis[maxn];
int dx[maxn],dy[maxn];
double diss(int x1,int y1,int x2,int y2) //计算两点距离
{
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
double radius(int p) //计算半径
{
double ans=min(abs(dx[p]-x),min(abs(dy[p]-y),min(abs(dx[p]-xx),abs(dy[p]-yy))));
for(int i=1;i<=n;i++)
{
if(vis[i]&&i!=p)
{
double dis=diss(dx[i],dy[i],dx[p],dy[p]);
ans=min(ans,max(dis-rx[i],0.0));
}
}
return ans;
}
void dfs(int nowcnt,double area) //area为拓展总面积 nowcnt为现在已经放置了几个
{
if(nowcnt==n)
{
maxans=max(maxans,area);
return ;
}
for(int i=1;i<=n;i++)
{
if(!vis[i])
{
vis[i]=true;
rx[i]=radius(i);
dfs(nowcnt+1,area+pi*rx[i]*rx[i]);
rx[i]=0;
vis[i]=false;
}
}
}
int main()
{
scanf("%d",&n);
scanf("%d%d%d%d",&x,&y,&xx,&yy);
double sum=abs(x-xx)*abs(y-yy); //矩形总面积
for(int i=1;i<=n;i++)
{
scanf("%d%d",&dx[i],&dy[i]);
}
dfs(0,0.0);
printf("%0.0lf",sum-maxans);
return 0;
}
2. P1120 小木棍
题意
将一些长度为 x 的等长木棍全部切成 n 段不超过50的小木棍,求长木棍长度 x 的最小长度。
思路
首先这个题是有个坑的,题目给出来了,输入的小木棍长度可能会有大于50的,因此我们需要筛掉它。
那么很显然这个题是一道搜索题,我们可以写搜索函数dfs(int nowcnt,int nxt,int lenlast,int len)
.上述参数分别表示: 现在在寻找第几根小木棍,我们寻找下一个拼接段应该从哪里开始找,当前这根拼接还需要多长,以及我们要拼成多长的木棍。搜索的复杂度这么高,对于 $n\le65$ 的数据肯定不能直接无脑搜,因此需要想想怎么优化。
首先要从大到小排序这个很关键的,因为你从大的先凑就能够保证后面选择的时候容错率更高一些。
很显然我们可以剪掉当 lenlast<0 的情况,这个地方我们可以在拼接的时候就判断,也可以在拼接后判断。
在寻找下一个拼接片段的时候,我们可以通过二分搜索来查找下一个不超过lenlast的片段,我选择了直接用STL的库中的lower_bound函数。
(其实因为是我的二分总是写炸)再就是我们对于相等片段的处理,很显然当前片段不符合情况那么与他等长的也都不会符合,因此我们可以直接循环筛掉。当然更优的方法可以提前处理一个跳表,直接跳到下一个与他不同的位置。
最后这个优化还是挺难想的,就是如果当前片段搜下去已经不符合情况,但是当前的lenlast是等于当前片段长度的,也就是说你正好用了尽可能满足条件的一个方案,也还是没达到目的,你们你继续往下搜,用比他还要劣的方案肯定也是不可能的,因此直接就break跳出循环不需要往下搜了。
不过就算加了这么多优化我还是T了三个点,直接 O2一开跑路了嘿嘿
代码实现
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iostream>
using namespace std;
const int maxn=70;
int n,a[maxn],temp,icnt=1;
int totlen,maxlen,cnt;
bool vis[maxn],finish;
int cmp(int a,int b)
{
return a>b;
}
void dfs(int nowcnt,int nxt,int lenlast,int len)
//nowcnt:现在正在拼接第几根
//nxt:我们应该从哪里开始检索
//lenlast:现在拼接还需要多少才能拼接完成
//len:每根木棍的理想长度
{
if(lenlast<0) return;
if(lenlast==0)
{
// printf("test\n");
if(nowcnt==cnt)
{
printf("%d\n",len);
finish=true;
return ;
}
int p=1;
for(p=1;p<=n;p++) if(!vis[p]) break;
vis[p]=true;
dfs(nowcnt+1,p+1,len-a[p],len);
if(finish) return ;
vis[p]=false;
}
else
{
int pos=lower_bound(a+nxt,a+1+n,lenlast,greater<int>())-a;
for(int i=pos;i<=n;i++)
{
// printf("what\n");
if(!vis[i]&&lenlast-a[i]>=0)
{
vis[i]=true;
dfs(nowcnt,i+1,lenlast-a[i],len);
if(finish) return;
vis[i]=false;
while(a[i+1]==a[i]) i++;
if(i==n) return;
if(lenlast-a[i]==0) break;
}
}
}
}
int main()
{
// freopen("test.in","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&temp);
if(temp>50) a[i]=0;
else a[i]=temp;
maxlen=max(maxlen,a[i]);
totlen+=a[i];
}
sort(a+1,a+1+n,cmp);
while(!a[n]) n--;
for(int l=maxlen;l<=totlen;l++)
{
finish=false;
if(totlen%l!=0) continue;
cnt=totlen/l;
vis[1]=true;
dfs(1,2,l-a[1],l);
vis[1]=false;
if(finish) return 0;
}
return 0;
}
3. YOKOF - Power Calculus
题意
给出一个正整数 n ,只能使用乘法或者除法,可以乘除 $x$ 或者过程中产生的中间值 $x^i$ ,输出使得 $x$ 变为 $x^n$ 所需的最少步数。$(n\le100)$
思路
很显然我们一直是对指数进行操作,看似是乘除,直接转化为指数的加减。因此我们需要记录一个状态数组来记录乘除中间所产生的 $x^i$ ,以便后续过程中使用。但是这道题直接搜索的话,又会超时,因为他把大量的时间浪费在高深度上,但是这个却不一定是最优解。因此需要用到迭代加深搜索(IDDFS).
迭代加深搜索(IDDFS)主要用于处理一些题目可能会搜到很深但是答案却不是最优的问题。有的时候dfs搜索的深度是无穷的,而且他的复杂度是呈指数级增长的,因此这其中某些情况就可以用IDDFS,在每次搜索的时候,我们给深度一个限制,当达到这个最大深度却没有得到答案的时候,就返回,然后逐步提升深度,这样我们就可以避免将时间浪费在那些无谓的高深度搜索上了。
$$ \sum_{i=0}^n2^i=2^{n+1}-1(指数级别增长实例) $$
代码实现
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int n;
int x[1005]; //用来记录每次生成的中间状态
bool dfs(int k,int dep,int maxdep)
{
if(k<=0||dep>maxdep||k<<(maxdep-dep)<n) return false;
if(k==n||k<<(maxdep-dep)==n) return true;
x[dep]=k;
for(int i=0;i<=dep;i++)
{
if(dfs(k+x[i],dep+1,maxdep)) return true; //对应乘法
if(dfs(k-x[i],dep+1,maxdep)) return true; //对应除法
}
x[dep]=0;
return false;
}
int main()
{
while(scanf("%d",&n)&&n)
{
for(int i=0;;i++)
{
if(dfs(1,0,i))
{
printf("%d\n",i);
break;
}
}
}
}