题解 P1026 【统计单词个数】

· · 题解

这里讲一下我和其他题解有点不一样的思路QAQ

首先珂以知道这是一道DP

这道题的思路和P1018乘积最大的思路有点像

把一个单词分成 k 份就相当于在单词中放置 (k-1) 条分割线

为方便说明,下文把 (k-1) 直接用 k 表示

f[i][j] 为在第 i 个字母后放第 j 个分割线,前 i 个单词所能获得的最大价值

注意每份单词不能为空

最后统计答案的时候在 f[i][k](k<=i<=n-k)+(i+1,n)对答案的贡献,扫一遍取个 max

case1:如何转移?

我的想法还是比较暴力的,首先初始化 f[i][1] 全部为初始化为(1,i)这个区间对答案的贡献

第一层循环j枚举正在放置第j个分割线(2<=j<=k)

第二层循环i枚举分割线放置的位置(j<=i<=n-1);

最后一层循环l枚举从前面的哪个状态转移过来

也就是在(f[l][j-1]+(l+i,i)这个区间对答案的贡献)中取 max (j-1<=l<=i-1)

rep(i,1,n-1)
 f[i][1]=query(1,i);
rep(j,2,k)
 rep(i,j,n-1)
  rep(l,j-1,i-1)
   f[i][j]=max(f[i][j],f[l][j-1]+query(l+1,i));
### case2:如何统计一个区间对答案的贡献 显然一个区间对答案的贡献就是一个区间中字典中单词个数(去重后) 题目中给出了一个限制:**当选用一个单词之后,其第一个字母不能再用。** 那我们就珂以依次对字典中的每个字符串进行匹配,如果匹配成功就将该字符匹配位置的开头标记为匹配过,下回如果匹配到同一位置既不合法,跳过该次匹配 ```cpp //本人习惯字符串下标从1开始 inline int cnt(int index,int l,int r){ int len=r-l-strlen(word[index]+1)+2,res=0;//len为匹配次数,res为该字符串的贡献 rep(i,0,len-1) rep(j,1,strlen(word[index]+1)) if((word[index][j]^str[l+i+j-1])||(trace[l+i])) break; else if(j==strlen(word[index]+1)) ++res,trace[l+i]=true; return res; } inline int query(int l,int r){ rep(i,l,r) trace[i]=false; int res=0; rep(i,1,s)//依次对每个字符串进行匹配 if(r-l+1>=strlen(word[i]+1))//如果区间长度>字符串长度 res+=cnt(i,l,r);//统计该字符串的贡献 return res; } ``` ## 祭上代码: ```cpp #include<bits/stdc++.h> #define rint register int #define rc register char #define rll register long long #define rep(i,j,k) for(rint i=j;i<=k;++i) #define dow(i,j,k) for(rint i=j;i>=k;--i) #define MAXN 205 #define N 10 int p,k,ans=0,s,n; char str[MAXN]; int f[MAXN][MAXN]; char word[N][MAXN]; bool trace[MAXN]; inline int max(int a,int b){return a>b?a:b;} inline int min(int a,int b){return a<b?a:b;}//手写较快 inline int cnt(int index,int l,int r){ int len=r-l-strlen(word[index]+1)+2,res=0;//len为匹配次数,res为该字符串的贡献 rep(i,0,len-1) rep(j,1,strlen(word[index]+1)) if((word[index][j]^str[l+i+j-1])||(trace[l+i])) break; else if(j==strlen(word[index]+1)) ++res,trace[l+i]=true; return res; } inline int query(int l,int r){ rep(i,l,r) trace[i]=false; int res=0; rep(i,1,s)//依次对每个字符串进行匹配 if(r-l+1>=strlen(word[i]+1))//如果区间长度>字符串长度 res+=cnt(i,l,r);//统计该字符串的贡献 return res; } void calc(){/程序核心dp rep(i,1,n-1) f[i][1]=query(1,i); rep(j,2,k) rep(i,j,n-1) rep(l,j-1,i-1) f[i][j]=max(f[i][j],f[l][j-1]+query(l+1,i)); rep(i,k,n-k) ans=max(ans,f[i][k]+query(i+1,n)); } void Debug(){//个人调试用 rep(i,1,s) printf("%s\n",word[i]+1); rep(i,1,n) rep(j,1,k) printf("f[%d][%d]=%d\n",i,j,f[i][j]); } int main(){ scanf("%d %d\n",&p,&k); --k;//分割线数量 rep(i,1,p) scanf("%s",str+20*(i-1)+1);//s每次输入把字符串拼接到下标为20*(i-1)+1上,+1是因为下标从1开始 n=strlen(str+1); scanf("%d",&s); rep(i,1,s) scanf("%s",word[i]+1);//下标从1开始 calc(); //Debug(); printf("%d\n",ans); } ``` ~~第一次提交忘了把Debug注释掉居然还有20?你谷评测机太玄学~~