[HNOI2004][bzoj1212] L语言 [Trie+dp]

题面

传送门

思路

无后效性

显然,不管某个前缀的理解方式是怎么样的,如果它能被理解,那么前面的决策对于后面的决策而言都是等价的

因此这题可以DP

DP方程

令$dp[i]$表示前缀i是否能被理解

那么,显然状态转移方程为:

$dp[i]=dp[i]||dp[j];;(s[j+1...i]\in D)$

也就是说,现在我们的问题转化为:求第i位往前数可以匹配的所有单词

因为这个往前数的方向和方式是唯一的,所以我们想到一个字符串数据结构:$Trie$

$Trie$

一个非常常规的想法,就是把所有单词插到$trie$里面,然后用这个往前数的子串去匹配单词

但是这样有一个问题:我们是从$trie$的某一个深层次位置往根匹配,而这样的开始节点可能有多个

这个问题出现的根本原因,是因为我们是拿一个后缀去匹配单词的,但是我们的$trie$以前缀方式保存

所以,我们只要把$trie$保存的方式从前缀变成后缀就好了

我们把所有单词反向,插入$trie$里面

求$dp[i]$的时候,从$s[i]$开始往前,$trie$从根节点开始,向下走,走到一个单词的结尾(实际上反过来以后就是单词的开头),就用这个位置更新$dp[i]$

总时间效率上限:

$O\left(m\left(10strlen(s)+O\left(Trie\right)\right)\right)=O\left(2\ast10^8\right)$

但是一般达不到这么多,我的代码最后一个点272ms,还是很稳的

实在不行,给评测机吸氧啊【雾】

Code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct node{//trie节点
    int num,son[27];
    node(){num=0;memset(son,0,sizeof(son));}
}x[210];
int n,m,dp[1000010],cnt;char a[1000010];
void add(char s[20]){//插入单词
    int len=strlen(s),cur=0,i;
    for(i=len-1;i>=0;i--){
        if(!x[cur].son[s[i]-'a']) x[cur].son[s[i]-'a']=++cnt;
        cur=x[cur].son[s[i]-'a'];
    }
    x[cur].num++;
}
void check(char s[],int pos){
    int cur=0,i,flag=0;
    for(i=pos-1;i>=0;i--){
        if(!x[cur].son[s[i]-'a']) break;
        cur=x[cur].son[s[i]-'a'];if(x[cur].num&&dp[i]) flag=1;
    }
    dp[pos]=flag;
}
int main(){
    scanf("%d%d",&n,&m);int i,len,l,j,ans;char s[20];
    for(i=1;i<=n;i++) scanf("%s",s),add(s);
    for(l=1;l<=m;l++){
        scanf("%s",a);memset(dp,0,sizeof(dp));
        dp[0]=1;len=strlen(a);ans=0;
        for(i=1;i<=len;i++){
            check(a,i);
            if(dp[i]) ans=max(i,ans);
        }
        printf("%d\n",ans);
    }
}
posted @ 2018-04-22 11:37  dedicatus545  阅读(196)  评论(0编辑  收藏  举报