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;
}
posted @ 2019-01-03 19:10  dummyummy  阅读(169)  评论(0编辑  收藏  举报