P1118 [USACO06FEB]Backward Digit Sums G/S
阿新 • • 發佈:2020-12-06
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,intv); //寫函式原型是(我的)好習慣! 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; //執行到這裡一定是沒有找到解 }