hdu4843 [noi2000] 古城之谜 (Trie+dp)

做出这道题关键在于理解语法。其中名词短语和动词短语给出的都是递归定义,可以转化为更直观的,
<名词短语> ::= {<辅词>} <名词>,
<动词短语> ::= {<辅词>} <动词>,
也就是以一个名词或动词结尾,前面可以加上任意多个辅词。而句子就是要以名词短语开头,后面的名词短语和动词短语交替出现。

分析出语法结构,就可以进行动态规划。定义词性
j={0,1,2,3}。
f[i][0][k]表示前i个字母,以i结尾的单词词性为n,构成了k个句子的最小单词数
f[i][1][k]表示前i个字母,以i结尾的单词词性为v,构成了k个句子的最小单词数
f[i][2][k]表示前i个字母,以i结尾的单词词性为a,后面该接v了,构成了k个句子的最小单词数
f[i][3][k]表示前i个字母,以i结尾的单词词性为a,后面该接n了,构成了k个句子的最小单词数
枚举可能以i结尾的单词,设它的词性为type,前一个单词结尾为j。则分类讨论f[i][0/1/2/3][k]可以由f[j][0/1/2/3][k/k-1]的哪些转移而来,具体可以见这个人的分析
找到最小的k,使得min{f[m,0,k],f[m,1,k]}有意义,则最小的句子数为k,单词数为min{f[m,0,k],f[m,1,k]}
时间复杂度为O(nm·maxlen),匹配单词的时候可以用Trie树,第三维状态必须使用滚动数组。
tips:滚动数组每次都要清inf。初值为f[0][0][0]=0

#include 
#include 
#define N 1005
#define M 6000
#define inf 0x3f3f3f3f
int n,m,maxlen,word[M][22],f[M][4][2],p;//word[i][j]表示以i开头,长度为j的子串是否为单词以及词性 
//f[i][0][k]表示前i个字母,以i结尾的单词词性为n,构成了k个句子的最小单词数
//f[i][1][k]表示前i个字母,以i结尾的单词词性为v,构成了k个句子的最小单词数
//f[i][2][k]表示前i个字母,以i结尾的单词词性为a,后面该接v了,构成了k个句子的最小单词数
//f[i][3][k]表示前i个字母,以i结尾的单词词性为a,后面该接n了,构成了k个句子的最小单词数
char s[M];
bool blank=0;
inline int max(int x,int y){return x>y?x:y;}
inline int min(int x,int y){return xstruct Trie{
    int val;//记录词性,因为可能有多种词性,用二进制1,10,100累加 
    Trie *son[26];
    Trie(){val=0;memset(son,0,sizeof(son));}
}*root,mempool[N*22],*cnt=mempool;
void insert(Trie *&p,char *s,int type){
    if(!p) p=cnt++;
    if(!*s){p->val|=type;return;}
    insert(p->son[(*s)-'a'],s+1,type);
}
int find(Trie *p,int st,int ed){
    for(int i=st;i<=ed;++i){
        if(!p->son[s[i]-'a']) return 0;
        p=p->son[s[i]-'a'];
    }return p->val;
}
int main(){
//  freopen("a.in","r",stdin);
    while(~scanf("%d",&n)){
        if(blank) puts("");
        else blank=1;
        root=cnt++;maxlen=0;p=0;int ans2=inf,ans1=0;
        memset(word,-1,sizeof(word));memset(f,0x3f,sizeof(f));
        for(int i=1;i<=n;++i){//建Trie 
            char s[25];scanf("%s",s);maxlen=max(maxlen,strlen(s));
            if(s[0]=='n') insert(root,s+2,1);
            else if(s[0]=='v') insert(root,s+2,2);
            else insert(root,s+2,4);
        }
        scanf("%s",s+1);m=strlen(s+1)-1;
        f[0][0][0]=0;p=!p;
        for(int k=1;k<=m;++k,p=!p){
            for(int i=1;i<=m;++i){
                f[i][0][p]=f[i][1][p]=f[i][2][p]=f[i][3][p]=inf;//每次都要清inf 
                for(int j=i-1;j>=i-maxlen&&j>=0;--j){//枚举上一个单词的结尾 
                    if(word[j+1][i-j]==-1) word[j+1][i-j]=find(root,j+1,i);//看是不是单词以及词性 
                    int type=word[j+1][i-j];
                    if(type&1){//n.
                        f[i][0][p]=min(f[i][0][p],f[j][1][p]+1);
                        f[i][0][p]=min(f[i][0][p],f[j][3][p]+1);
                        f[i][0][p]=min(f[i][0][p],f[j][0][!p]+1);
                        f[i][0][p]=min(f[i][0][p],f[j][2][!p]+1);
                    }
                    if(type&2){//v.
                        f[i][1][p]=min(f[i][1][p],f[j][0][p]+1);
                        f[i][1][p]=min(f[i][1][p],f[j][2][p]+1);
                    }
                    if(type&4){//a.
                        f[i][2][p]=min(f[i][2][p],f[j][0][p]+1);
                        f[i][2][p]=min(f[i][2][p],f[j][2][p]+1);

                        f[i][3][p]=min(f[i][3][p],f[j][1][p]+1);
                        f[i][3][p]=min(f[i][3][p],f[j][3][p]+1);
                        f[i][3][p]=min(f[i][3][p],f[j][0][!p]+1);
                        f[i][3][p]=min(f[i][3][p],f[j][1][!p]+1);
                    }
                }
            }
            ans2=min(f[m][0][p],f[m][1][p]);//最小单词数 
            if(ans2!=inf){ans1=k;break;}//合法了,那此时的k就是最小的句子数 
        }
        printf("%d\n%d\n",ans1,ans2);
    }
    return 0;
}

你可能感兴趣的:(其他oj,Trie)