1. 程式人生 > 實用技巧 >P1118 [USACO06FEB]Backward Digit Sums G/S

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; //執行到這裡一定是沒有找到解 }