Sicily 1142 排序 (SOJ 1142)「搜索剪枝」

0.00 avg. rating (0% score) - 0 votes
转载请注明: 吹水小镇 | reetsee.com
原文链接地址: http://blog.reetsee.com/archives/143

原题地址:点击打开链接

看到这道题目我立刻反应出来是《编程之美》里面的烧饼问题,在看烧饼问题之前,我以为这题是有多项式复杂度解法的····但是后来发现暂时还没有。

其实编程之美里面写的代码比较长,实际在这里写应该不用那么长。

传统的搜索剪枝是深搜+剪枝,每次找到更小的值就记录下来,如果当前递归的深度超过当前最优值,那么就剪掉。如果可以记录同一深度已经访问过的状态,那么可以做出一些优化。

这题烧饼问题有一个地方可以留意一下就是最少操作数的上界不会超过2*n,因为总是可以每次都将最大的数字先倒序排在首位,然后再倒一次排在末尾,因此每个数字最多通过2次操作就可以就位。

这一题直接写我没有试过,因为我百度的时候找到了一种挺妙的方法,原地址参见:点击打开链接

——————正文——————

现在觉得解题报告还是不要太详细了,免得以后看的时候不动脑思考。

我参考的做法是先找到操作次数的下界,为什么前后两个数之差的绝对值在2或以上(我们已经将不同的数字映射为1,2,3,4,5…n),就必定有一次操作呢?因为如果相差为1,那么可能前面/后面也有一些相差为1的数字对,这样在反转的时候可以顺便将当前的数字对也翻转,但是如果当前的数字对相差在2或以上,那么即便经过一次翻转,也至少还要一次翻转来让它们回到自己的位置。

参考做法中比较巧妙的地方就是先找到下界,每次最多的递归深度就是下界,如果在这个下界没有找到解,那么将下界加1,再次进行搜索。这样可以避免一开始深搜的时候递归太深,但是这种解法也有一个问题,就是如果正解是比较大的话,那么这个方法就是比较消耗时间,因为每次提升下界后,之前计算过的过程会被重复一次,至于怎么优化,我就没有思考了,因为心情不太好,补选赛被虐了。

代码我就几乎直接抄过来了,如下:

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<cmath>
using namespace std;
int a[30];
int num[30];
int n,bound;
bool cmp(int aa,int bb)
{
    return num[aa]<num[bb];
}
int minimum()
{
    int ans=0;
    for(int i=1;i<n;++i)
     ans+=(abs(a[i]-a[i-1])>1?1:0);
    return ans;
}
bool check()
{
    for(int i=1;i<n;++i)
     if(a[i]<a[i-1])
      return false;
    return true;
}
bool compute(int left)
{
    if(left==0)
      return check();
    if(minimum()>left)
      return false;  //如果当前字符串的下界已经超出剩余的可操作数,则退出
    for(int i=2;i<=n;++i)
    {
        reverse(a,a+i);
        if(compute(left-1))
          return true;
        reverse(a,a+i);
    }
    return false;
}
int main()
{
    //freopen("in","r",stdin);
    //freopen("out","w",stdout);
    scanf("%d",&n);
    for(int i=0;i<n;++i)
    { 
        a[i]=i;
        scanf("%d",num+i);
    }
    sort(a,a+n,cmp);
    bound=minimum();
    while(!compute(bound++)){}
    printf("%d\n",--bound);
}

 

从VIM上面复制下来的代码总是在这里就位置错乱。哎,每次都要调挺麻烦的,哪位好朋友有解决方法烦请赐教

发表评论

电子邮件地址不会被公开。 必填项已用*标注