給定 n (≤ 250),求和為 n 的「單峰回文分解」個數。定義:序列先嚴格遞增後嚴格遞減,且左右對稱。例如 n=5:(5), (1,3,1), (1,1,1,1,1) 共 3 種。
去掉中間峰值,剩餘左右兩側對稱。設峰值為 m,左右各為嚴格遞增序列和為 (n-m)/2。等價於:將 n 拆分為若干部分,最大部分 ≤ m,且... 實際上直接 DP 更簡單。
f[i][j] = 將 i 拆分為若干正整數,且每個數 ≤ j 的方案數(順序無關,即整數分區)。
f[i][j] = f[i][j-1] + f[i-j][j] (j ≤ i)f[i][j-1]f[i-j][j]f[0][j]=1, f[i][0]=0 (i>0), f[i][1]=1枚舉峰值 m (1~n),剩餘 n-m 必為偶數,設 s = (n-m)/2。左右兩側各為和為 s、最大值 < m 的嚴格遞增序列。對應分區數為 f[s][m-1]。總和:
ans = 1 + Σ_{m=1}^{n-1} f[(n-m)/2][m-1] (僅當 n-m 為偶數)
初始 1 對應序列 (n) 本身。
#include
#include
using namespace std;
const int MAXN = 250;
long long dp[MAXN+1][MAXN+1];
long long f(int m, int n) { // 和為 m,最大值 ≤ n
if (dp[m][n] != -1) return dp[m][n];
if (m == 0) return dp[m][n] = 1;
if (n == 0) return dp[m][n] = 0;
if (n == 1) return dp[m][n] = 1;
if (n > m) return dp[m][n] = f(m, m);
return dp[m][n] = f(m, n-1) + f(m-n, n);
}
int main() {
memset(dp, -1, sizeof(dp));
int n;
while (scanf("%d", &n) == 1 && n) {
long long ans = 1; // (n)
for (int mid = 1; mid < n; ++mid) {
if ((n - mid) % 2 == 0) {
int half = (n - mid) / 2;
ans += f(half, mid - 1);
}
}
printf("%lld\n", ans);
}
return 0;
}
long long (__int64)