1. 程式人生 > >NOI2009 管道取珠 神仙DP

NOI2009 管道取珠 神仙DP

原題連結
原題讓求的是\(\sum\limits a_i^2\),這個東西直接求非常難求。我們考慮轉化一下問題。
首先把\(a_i^2\)拆成\((1+1+...+1)(1+1+...+1)\),兩個括號中的\(1\)都有\(a_i\)個。為什麼要這樣呢?仔細理解一下拆開後的式子,是不是就是相當於分別操作兩次,問最終序列相同的方案數?
這樣的話\(DP\)就比較好想了,設\(f[i][j][k][l]\)表示第一次操作上管道已經取了\(i\)個,下管道取了\(j\)個,第二次操作上管道取了\(k\)個,下管道取了\(l\)個時相同的方案數。
顯然,這樣會炸空間。又發現\(i+j=k+l\),所以最後一維可以扔了。
轉移方程就不詳細敘說了,寫在程式碼裡吧。最後還要來一個滾動陣列優化空間!

#include <bits/stdc++.h>

using namespace std;

#define N 500
#define MOD 1024523

int n, m, f[2][N+5][N+5];
char s1[N+5], s2[N+5];

int main() {
    #ifndef ONLINE_JUDGE
        freopen("testdata.in", "r", stdin);
        freopen("testdata.out", "w", stdout);
    #endif
    scanf("%d%d", &n, &m);
    scanf("%s%s", s1+1, s2+1);
    for(int i = 1, j = n; i < j; ++i, --j) swap(s1[i], s1[j]);
    for(int i = 1, j = m; i < j; ++i, --j) swap(s2[i], s2[j]);
    f[0][0][0] = 1;
    int flag = 0;
    for(int i = 0; i <= n; ++i, flag ^= 1) {
        memset(f[flag^1], 0, sizeof f[flag^1]);
        for(int j = 0; j <= m; ++j)
            for(int k = 0, l; k <= min(n, i+j); ++k) {
                l = i+j-k;
                if(l < 0 || l > m) continue;
                //分4種情況分別轉移
                if(s1[i+1] == s1[k+1]) f[flag^1][j][k+1] = (f[flag^1][j][k+1]+f[flag][j][k])%MOD;
                if(s2[j+1] == s1[k+1]) f[flag][j+1][k+1] = (f[flag][j+1][k+1]+f[flag][j][k])%MOD;
                if(s1[i+1] == s2[l+1]) f[flag^1][j][k] = (f[flag^1][j][k]+f[flag][j][k])%MOD;
                if(s2[j+1] == s2[l+1]) f[flag][j+1][k] = (f[flag][j+1][k]+f[flag][j][k])%MOD;
            }
    }
        
    printf("%d\n", f[flag^1][m][n]);
    return 0;
}