[NOI2000] 古城之谜

Description

给定 n 和 n 个信息,每个信息包含一个词性 a (只有三种:名,动,辅)和对应的词 mot ,形为“ \(a.mot\) ”。(一次可能多词性)

最后给一个长度不大于 \(5KB\) 的冰峰文文章,将这篇冰峰文文章划分为最少的句子,在这个前提下,将文章划分为最少的单词时,求划分的句子数量和单词数量。

划分标准:

(别问我为什么盗图。。

\(1\leq n\leq 10^3\ \ \ \ mot.len\leq 20\)

Solution

首先要搞懂题目中的图是什么玩意(我真的看了好久都没看懂。。)

所有语法简述下来就是:


1.名词短语是许多个辅词加一个名词组成的。

2.动词短语是许多个辅词加一个动词组成的。

3.一个句子以名词短语开头,名词短语和动词短语交替出现而组成的。

4.文章为多句话组成。


所以对于任意词,有四种类型:


1.名词。

2.动词。

3.辅名词的辅词。

4.辅动词的辅词。


状态应该很自然了。。(要什么设什么呗)

\(f[j][i][0]\) 指前 \(i\) 个字母,最后一个单词是名词,构成了 \(j\) 个句子的最小单词数。

\(f[j][i][1]\) 指前 \(i\) 个字母,最后一个单词是动词,构成了 \(j\) 个句子的最小单词数。

\(f[j][i][2]\) 指前 \(i\) 个字母,最后一个单词是辅词,后面要接动词,构成了 \(j\) 个句子的最小单词数。

\(f[j][i][3]\) 指前 \(i\) 个字母,最后一个单词是辅词,后面要接名词,构成了 \(j\) 个句子的最小单词数。

状态转移方程就按照语法看能否转移就行

\(f[j][i][0] \Longrightarrow \min{(f[j][k][1/3],f[j-1][k][0/2])}\)

\(f[j][i][1] \Longrightarrow \min{(f[j][k][0/2])}\)

\(f[j][i][2] \Longrightarrow \min{(f[j][k][0/2])}\)

\(f[j][i][3] \Longrightarrow \min{(f[j][k][1/3],f[j-1][k][0/1])}\)

实际上看式子的话, \(j\) 那一维可以滚动起来。(虽然不滚掉好像问题不大,但省空间多好。。)

最后答案就是按题目来,求一个最小的 \(ans\) ,存在 \(f[ans][len][0/1]\) ,如果都存在,取较小值。

\(DP\) 这里就结束了,考虑如何实现。

明显 \(DP\) 的复杂度不允许我们每次枚举所有单词再去比较。

所以想到了用一个比较实用的东西 \(trie\) 可以把速度拉起来。

基本上这题就搞定了,就是注意一定把数组开稍微大点(我因为忽略数组大小而傻乎乎地去调了半个小时程序了)

Code

#include<bits/stdc++.h>
#define reg register
using namespace std;
typedef long long ll;
const int N=1e3+10,M=6e3+10,K=3e4+10;
const int INF=0x3f3f3f3f;
int n,m,mlth,f[2][M][4],tri[M][24],tot,op,ans1,ans2;
char sw[M],sd[M];
struct trie{
	int tr[26],opt,it;
	inline void clear(){
		memset(tr,0,sizeof(tr));
		it=-1;opt=0;
	}
}trie[K];
inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
	return s*w;
}
inline void insert(char s[],int lth,int opt){
	int id=0,val;
	for(int i=2;i<lth;++i){
		val=s[i]-'a';
		if(!trie[id].tr[val]){
			trie[++tot].clear();
			trie[tot].it=val;
			trie[id].tr[val]=tot;
		}
		id=trie[id].tr[val];
	}
	trie[id].opt|=opt;
}
inline int find(int lt,int rt){
	int id=0,val;
	for(int i=lt;i<=rt;++i){
		val=sd[i]-'a';
		if(!trie[id].tr[val])return 0;
		id=trie[id].tr[val];
	}
	return trie[id].opt;
}
inline void mian(){
	ans1=0;ans2=INF;
	trie[0].clear();
	memset(tri,-1,sizeof(tri));
	memset(f,INF,sizeof(f));
	for(int i=1;i<=n;++i){
		scanf("%s",sw);m=strlen(sw);
		mlth=max(m,mlth);
		if(sw[0]=='n')insert(sw,m,1);
		else if(sw[0]=='v')insert(sw,m,2);
		else if(sw[0]=='a')insert(sw,m,4);
	}
	scanf("%s",sd+1);m=strlen(sd+1)-1;
	f[0][0][0]=0;
	for(int lin=1;lin<=m;++lin){
		int now=op^1,pre=op;
		for(int i=1;i<=m;++i){
			memset(f[now][i],INF,sizeof(f[now][i]));
			int lim=max(i-mlth,0);
			for(int j=i-1;j>=lim;--j){
				if(tri[j+1][i-j]==-1)
					tri[j+1][i-j]=find(j+1,i);
				int opti=tri[j+1][i-j],nowi,prei;
				if(opti&1){
					nowi=min(f[now][j][1],f[now][j][3]);
					prei=min(f[pre][j][0],f[pre][j][2]);
					f[now][i][0]=min(f[now][i][0],nowi+1);
					f[now][i][0]=min(f[now][i][0],prei+1);
				}//不能有else
				if(opti&2){
					nowi=min(f[now][j][0],f[now][j][2]);
					f[now][i][1]=min(f[now][i][1],nowi+1);
				}//不能有else
				if(opti&4){
					nowi=min(f[now][j][0],f[now][j][2]);
					f[now][i][2]=min(f[now][i][2],nowi+1);

					nowi=min(f[now][j][1],f[now][j][3]);
					prei=min(f[pre][j][0],f[pre][j][1]);
					f[now][i][3]=min(f[now][i][3],nowi+1);
					f[now][i][3]=min(f[now][i][3],prei+1);
				}//不能有else
			}
		}
		ans2=min(f[now][m][0],f[now][m][1]);
		if(ans2!=INF){ans1=lin;break;}
		op^=1;
	}
	printf("%d\n%d\n",ans1,ans2);
}
int main(){
	n=read();
	mian();
	return 0;
}
posted @ 2021-07-08 13:05  Illusory_dimes  阅读(86)  评论(0编辑  收藏  举报