• 【BashuOJ2963】数字游戏-DFS+剪枝


    测试地址:数字游戏
    题目大意:给定一个1~n的排列A,每次将相邻两项相加,构成新的序列,例如{3,1,2,4}经过一次操作后变为{4,3,6},可以知道每次序列的长度都会减1,最后会得到一个整数sum,现在给你这个sum,请求出一开始的排列A,要求字典序最小。
    做法:本题需要使用DFS+剪枝来解决。
    首先我们思考,什么样的排列可以使最后的整数等于sum。我们把A1,A2,...,An作为未知数计算最后整数的值,可以发现,最后得到的式子中,Ai项的系数恰是杨辉三角形中第n层的第i个数,具体就不证明了。那么这样我们就可以根据排列来算最后的整数了。
    枚举所有排列是O(n!)的,然而本题中n最大为20,需要考虑剪枝。在这里我们需要用到一个叫做排序不等式的东西,具体来说,设x1x2...xny1y2...yn,要将这两个数列中的数字一一配对,那么最后每对数的乘积之和大于等于x1yn+x2yn1+...+xny1且小于等于x1y1+x2y2+...+xnyn。那么这个东西于这道题来说有什么用呢?在搜索的时候,我们知道剩下的还没选的数字,还知道要和它们配对的系数,那么我们就可以求出它们配对后的和的最大值和最小值,如果目前的和加上这个最大值仍然赶不上sum,那么就不用继续搜下去了,同理,如果目前的和加上最小值已经比sum大,也不用继续搜下去了。
    这样剪枝后,还剩下两个测试点TLE,怎么办呢?我们知道,杨辉三角中每一层的数都是左右对称的,所以对于一个排列,如果它可行,那么调换对称的两个位置上的数,它也是可行解。反之,如果它不是可行解,那么调换过来也不可能是可行解。这就给我们提供了剪枝的思路:对于现在枚举到的一位,如果它处于后半部分,那么它如果小于前面和它对称位置上选择的数,是不可能找到可行解的。采取反证法,如果这样找到了可行解,那么调换这两位可以找到一个字典序更小的解,然而之前并没有找到这样的解,所以这种情况下不可能找到可行解,就可以剪掉了。再加入这个剪枝后就可以AC了。
    以下是本人代码:

    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    int n,sum,ans[25]={0},c[25][25]={0};
    bool vis[25]={0};
    
    void solve(int step,int &maxx,int &minx)
    {
        maxx=minx=0;
        int j1=1,j2=n;
        for(int i=0;n-i>=i+1;i++)
        {
            if (!ans[i+1])
            {
                while(vis[j1]) j1++;
                while(vis[j2]) j2--;
                maxx+=j1*c[n][i+1];j1++;
                minx+=j2*c[n][i+1];j2--;
            }
            if (!ans[n-i]&&n-i>i+1)
            {
                while(vis[j1]) j1++;
                while(vis[j2]) j2--;
                maxx+=j1*c[n][i+1];j1++;
                minx+=j2*c[n][i+1];j2--;
            }
        }
    }
    
    bool dfs(int step,int now)
    {
        if (step==n+1)
        {
            if (now==sum) {for(int i=1;i<=n;i++) printf("%d ",ans[i]);return 1;}
            else return 0;
        }
        int maxx,minx;
        solve(step,maxx,minx);
        if (now+maxx<sum) return 0;
        if (now+minx>sum) return 0;
        int i=1;
        if (step>n-step+1) i=ans[n-step+1]+1;
        for(;i<=n;i++)
            if (!vis[i])
            {
                ans[step]=i;
                vis[i]=1;
                if (dfs(step+1,now+i*c[n][step])) return 1;
                ans[step]=0;
                vis[i]=0;
            }
        return 0;
    }
    
    int main()
    {
        scanf("%d%d",&n,&sum);
        c[1][1]=1;
        for(int i=2;i<=n;i++)
            for(int j=1;j<=i;j++)
                c[i][j]=c[i-1][j-1]+c[i-1][j];
        dfs(1,0);
    
        return 0;
    }
    
  • 相关阅读:
    无重复字符的最长子串
    有效的括号
    最长公共前缀
    罗马数字转整数
    Android解析JSON数据异步加载新闻图片
    回文数
    Java从Json获得数据的四种方式
    JavaMD5加密工具类
    div模仿select效果二:带搜索框
    BG雪碧图制作要求
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793602.html
Copyright © 2020-2023  润新知