【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;
}
posted @ 2017-09-10 13:09  Maxwei_wzj  阅读(108)  评论(0)    收藏  举报