[TJOI2018]游园会 - 题解

题意:
对于每一个\(i\)求满足如下条件的字符串的数目

  1. 长度为\(n\)且只出现过'N','O','I'三种字符
  2. 和一个长度为\(k\)的模式串的最长公共子序列长度恰好为\(i\)
  3. 不含"NOI"这个子串

题解:DP套DP。
首先考虑最长公共子序列怎么求。令\(dp[i][j]\)表示当前串的第\(i\)位,模式串的第\(j\)位为止最长公共子序列长度为多少,转移方程显然,\(dp[i][j]\)可以滚动数组优化。
\(f[u][i][j]\)表示已经从\(1\)\(n\)计算到第\(u\)个字符,当前\(dp[u]\)的状态\(i\),”NOI”中以完成前\(j\)位的方案数,\(u\)可以滚动数组优化空间,关于\(j\)的转移方程显然。对于\(i\)的转移,只要进行最长公共子序列的动态规划转移即可。
考虑优化\(i\)的空间。对于\(dp[i][j]\)\(dp[i][j-1]\),前者至多比后者多\(1\),所以可以差分\(dp[u]\),然后二进制压缩空间。
\(6000 ms\) 能过。

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
int n, k, sz[32768], a[40], b[40];
char T[40];
int Now = 0;
ll ans[40], f[2][32768][3];

inline int Max(int x, int y) { return x > y? x : y; }
void init() {
    scanf("%d%d%s", &n, &k, T + 1);
    for(int i = 1; i < (1 << k); i++)
        sz[i] = sz[i / 2] + (i & 1);
    f[Now][0][0] = 1;
}
void del_hsh(int x) {
    for(int i = 0; i < k; i++) a[i + 1] = (x >> i) & 1;
    for(int i = 1; i <= k; i++) a[i] += a[i - 1];
}
int get_hsh() {
    int sum = 0;
    for(int i = 0; i < k; i++) sum |= (b[i + 1] - b[i]) << i;
    return sum;
}
void dp(int u, int x, int h, char c, ll val) {
    del_hsh(x);
    for(int i = 1; i <= k; i++)
        b[i] = Max(a[i], Max(b[i - 1], a[i - 1] + (c == T[i])));
    int y = get_hsh(); f[u][y][h] = (f[u][y][h] + val) % mod;
}
void solve() {
    for(int i = 1; i <= n; i++) {
        int New = Now ^ 1;
        for(int j = 0; j < (1 << k); j++)
            for(int h = 0; h < 3; h++) f[New][j][h] = 0;
        for(int j = 0; j < (1 << k); j++) {
            if(f[Now][j][0])
                dp(New, j, 1, 'N', f[Now][j][0]),
                dp(New, j, 0, 'O', f[Now][j][0]),
                dp(New, j, 0, 'I', f[Now][j][0]);
            if(f[Now][j][1])
                dp(New, j, 1, 'N', f[Now][j][1]),
                dp(New, j, 2, 'O', f[Now][j][1]),
                dp(New, j, 0, 'I', f[Now][j][1]);
            if(f[Now][j][2])
                dp(New, j, 1, 'N', f[Now][j][2]),
                dp(New, j, 0, 'O', f[Now][j][2]);
        }
        Now = New;
    }
}
void print() {
    for(int i = 0; i < (1 << k); i++)
        for(int j = 0; j < 3; j++)
            ans[sz[i]] = (ans[sz[i]] + f[Now][i][j]) % mod;
    for(int i = 0; i <= k; i++) printf("%lld\n", ans[i]);
}
int main() {
    init(),solve(),print();
    return 0;
}

转载于:https://www.cnblogs.com/daniel14311531/p/10205588.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值