zoukankan      html  css  js  c++  java
  • P1118 [USACO06FEB]Backward Digit Sums G/S

    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; //执行到这里一定是没有找到解
    }

     

    因上求缘,果上努力~~~~ 作者:每天卷学习,转载请注明原文链接:https://www.cnblogs.com/BlairGrowing/p/14093456.html

  • 相关阅读:
    【设计模式】模板模式
    【设计模式】适配器模式
    【设计模式】观察者模式
    【设计模式】原型模式
    【设计模式】建造模式
    【Android Studio】Android Studio 安装及设置
    【设计模式】工厂模式(静态工厂模式、工厂方法模式、抽象工厂模式)
    【Linux高级驱动】LCD logo
    【Linux高级驱动】LCD驱动框架分析
    【系统移植】JNI
  • 原文地址:https://www.cnblogs.com/BlairGrowing/p/14093456.html
Copyright © 2011-2022 走看看