CF633C Spy Syndrome 2(字典树+dp)

CF633C Spy Syndrome 2

原题链接

说是 d p dp dp 但是感觉说是一个记忆化也可以。我们定义一个 d p dp dp 数组,其 d p [ i ] dp[i] dp[i] 含义为将加密串前 i i i 位都解密完成使用的最后一个单词的下标(从 1 1 1 开始),如果不能用现有单词将加密串前 i i i 个字符解密,那么 d p [ i ] = = 0 dp[i]==0 dp[i]==0

那么首先我们可以将所有单词加入一颗字典树,注意忽略掉大小写,我们只要在终点记录下此时是下标为多少的单词即可(因为加密串不区分大小写,所以如果两个串忽略大小写一样,那么最后答案选哪个都可以,所以我们只要保留一个就可以)。

为什么我们求出 d p [ n ] dp[n] dp[n] 就可以解出答案呢。因为我们得知 d p [ n ] dp[n] dp[n] 就首先可以得知最后一个用的是哪个单词,然后我们可以令 n = n − 单 词 长 度 n = n - 单词长度 n=n 得知新的 d p [ n ] dp[n] dp[n] ,这样以此类推我们就可以得知全部的单词。(首先题目保证了有解,所以 d p [ n ] dp[n] dp[n] 一定有解,我们求出 d p [ n ] dp[n] dp[n] 是保证了前 n n n 个都解密的,所以往前推的时候不可能无解)

我们现在就来求得 d p dp dp 数组,首先第一重循环从小到大枚举加密串下标,我们期望求出此点的 d p [ i ] dp[i] dp[i] ,那么第二重循环就是从 i i i 0 0 0 (因为加密的时候翻转了)在字典树上跳了,其实就是枚举其可能构成的最后一个单词,我们找到合理的 d p [ i ] dp[i] dp[i] 的条件自然是,我们跳到某个点 j j j 时, 字典树上有单词终点的标记,且 d p [ j − 1 ] dp[j -1] dp[j1] 不为 0 0 0 ,符合这种条件就可以将 d p [ i ] dp[i] dp[i] 更新并退出第二重循环,我们只要找到某一种情况可以使得解密前 i i i 个字符就好,如果我们遍历所有情况都找不到,那么 d p [ i ] dp[i] dp[i] 只能为 0 0 0 了。最终我们就可以利用第三段中讲述的方法,用 d p [ n ] dp[n] dp[n] 一步步推出每个单词,最终倒序输出即可。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 1e6 + 2;

int n, m;
int p[N][30], tot, dp[100005];
int idx[N];
string all, s[100005];
vector<string> ans;

void insert(string x, int id) {
    int root = 0;
    for (int i = 0; i < x.length(); ++i) {
        int cur;
        if (x[i] >= 'a') cur = x[i] - 'a';
        else cur = x[i] - 'A';
        if (!p[root][cur]) p[root][cur] = ++tot;
        root = p[root][cur];
    }
    idx[root] = id;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    scanf("%d", &n);
    cin >> all;
    scanf("%d", &m);
    for (int i = 1; i <= m; ++i) {
        cin >> s[i];
        insert(s[i], i);
    }
    for (int i = 0; i < all.length(); ++i) {
        int root = 0;
        for (int j = i; j >= 0; --j) {
            int cur = all[j] - 'a';
            if (!p[root][cur]) break;
            root = p[root][cur];
            if (idx[root] && (j == 0 || dp[j - 1])) {
                dp[i] = idx[root];
                break;
            }
        }
    }
    int sta = n - 1;
    while(dp[sta]) {
        ans.push_back(s[dp[sta]]);
        sta = sta - s[dp[sta]].length();
    }
    for (int i = (int)ans.size() - 1; i >= 0; --i) {
        cout << ans[i] << ' ';
    }
    cout << endl;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值