码迷,mamicode.com
首页 > 其他好文 > 详细

P1118 [USACO06FEB]Backward Digit Sums G/S

时间:2020-12-10 11:35:54      阅读:12      评论:0      收藏:0      [点我收藏+]

标签:开始   大小   reset   暴力   枚举   set   div   ext   next   

P1118 [USACO06FEB]Backward Digit Sums G/S

技术图片

 

 技术图片

 

 

题解:
??(1)暴力法。对1~N这N个数做从小到大的全排列,对每个全排列进行三角形的计算,判断是否等于N。
??对每个排列进行三角形计算,需要O(N2)次。例如第1行有5个数{a,b,c,d,e},那么第2行计算4次,第3行计算3次…等等,总次数是O(N2)的。
??a????b????c????d????e
????a+b????b+c???c+d???d+e
??????a+2b+c?b+2c+d?c+2d+e
???????a+3b+3c+d?b+3c+3d+e
??????????a+4b+6c+4d+e
??共有N!=4亿个排列,总复杂度是O ( N ! N 2 ) 的,显然会超时。
??2)三角计算优化+剪枝。
??1)三角计算的优化。对排列进行三角形计算,并不需要按部就班地算,比如{a,b,c,d,e}这5个数,直接算最后一行的公式a+4b+6c+4d+e就好了,复杂度是O(N)的。

不同的N有不同的系数,比如5个数的系数是{1,4,6,4,1},提前算出所有N的系数备用。可以发现,这些系数正好是杨辉三角。
??2)剪枝。即使有了杨辉三角的优化,总复杂度还是有O(N!N),所以必须进行最优性剪枝。对某个排列求三角形和时,如果前面几个元素和已经大于sum,

那么后面的元素就不用再算了。例如,N=9时,计算到排列{2,1,3,4,5,6,7,8,9},如果前5个元素{2,1,3,4,5}求和已经大于sum,那么后面的{6,7,8,9}~{9,8,7,6}都可以跳过,

下一个排序从{2,1,3,4,6,5,7,8,9}开始。本题sum≤12345,和不大,用这个简单的剪枝方法可以通过。
??3)可以用DFS求全排列,也可以直接用STL 的next_permutation()求全排列。

 

#include <cstdio>
using namespace std;

int n,sum;
//以下所有数组的大小都比所需值稍大,是为了防止越界
int visited[25]={0}; //防止重复选数,这是 dfs 枚举排列的要点
int ans[25]; //放置答案
int pc[25];//构造所有i C n-1

int dfs(int i,int num,int v); //写函数原型是(我的)好习惯!

int main(void){
    scanf("%d%d",&n,&sum);
    //下面构造杨辉三角(即组合数表)
    pc[0]=pc[n-1]=1; //杨辉三角性质,两边都是1
    if (n>1)
        for (int i=1;i*2<n;i++)
            pc[i]=pc[n-1-i]=(n-i)*pc[i-1]/i; //利用杨辉三角对称性和组合数公式计算
    //下面枚举计算
    if (dfs(0,0,0)) //0 仅起占位符作用
        for (int i=1;i<=n;i++)
            printf("%d ",ans[i]); //输出答案
    return 0;
}

int dfs(int i,int num,int v){
//参数说明:i 表示已经枚举了前 i 个数(数的序号从 1 开始),num 表示第 i 个数是 num,v 表示前 i 个数的“和”为 v
//返回值说明:返回 0 表示不行(不可能),返回 1 表示找到了可行解。利用返回值就可以在找到第一个解后直接返回了
    if (v>sum) //“剪枝”,及时排除不可能情况,加速枚举
        return 0; //不可能
    if (i==n){ //已经枚举了前 n 个(全部),判断一下是否是可行解
        if (v==sum){
            ans[i]=num; //放置解
            return 1;
        }
        else
            return 0;
    }
    visited[num]=1; //标记一下“第 i 个数的值已经使用过了”
    //下面寻找第 i+1 个数
    for (int j=1;j<=n;j++){
        if (!visited[j] && dfs(i+1,j,v+pc[i]*j)){ //v+pc[i]*j表示前(i+1)个数的“和”
            //注意,如果数的序号从 1 开始,那么第 i 个数的系数实际上是 (i-1) C (n-1)
            //执行到这里表示已经找到了可行的解
            ans[i]=num;
            return 1;
        }
    }
    visited[num]=0; //如果没有找到,一定记得复位,为进一步的寻找做准备
    return 0; //执行到这里一定是没有找到解
}

 

P1118 [USACO06FEB]Backward Digit Sums G/S

标签:开始   大小   reset   暴力   枚举   set   div   ext   next   

原文地址:https://www.cnblogs.com/BlairGrowing/p/14093456.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!