=== ===
=== ===
题解
首先这个题显然是应该考虑字符合并,一开始看到这个题的时候想到了某个名字就叫做【字符合并】的撞鸭DP题目。。但是那个题和这个题还是有很多不同的地方的,首先这个题可能合并到最后串长仍然很大所以肯定不能撞鸭,并且合并过的字符可能还可以继续合并,所以好像有点GG。。
然而这个题可以用某种叫做【看数据范围猜做法】+【Loli的NOIP模拟题肯定不会有什么类似O(n85log3n)这样的玄学复杂度】的不靠谱方法知道这题复杂度基本上就是O(n4)的DP。。并且对于一段区间[i..j]来说,它最后消成什么东西就只跟把它劈成两半区间然后这两半区间分别消成什么东西有关,然后考试的时候就“嘣”的一下想到可以用f[i][j][k]表示[i..j]这一段能否合并成k这个字符,那么设原串为s,一开始所有f[i][i][s[i]]都赋值为true,然后DP的时候枚举断点。这里要是再用O(26∗26)的复杂度枚举两半区间分别消成什么字符的话肯定会T,并且判断也是个问题,那么可以直接枚举给定的合并规则c1→c2c3,如果左边能合并成c2并且右边能合并成c3,那么中间这个大区间就能合并成c1。这样就能用O(n4)的时间搞定f数组。统计答案的时候再用一个g[i][j]表示[i..j]这一段最少消成几个字符,仍然枚举断点来递推就可以了。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,len,g[60][60];
char s[60],from[60],to[60][2];
bool f[60][60][30];
int main()
{
freopen("ancestor.in","r",stdin);
freopen("ancestor.out","w",stdout);
gets(s+1);
len=strlen(s+1);
scanf("%d",&n);
for (int i=1;i<=n;i++){
char c=getchar();
while (c<'a'||c>'z') c=getchar();
from[i]=c;
for (int j=0;j<=1;j++){
c=getchar();
while (c<'a'||c>'z') c=getchar();
to[i][j]=c;
}
}
memset(f,false,sizeof(f));
for (int i=1;i<=len;i++)
f[i][i][s[i]-'a'+1]=true;
for (int l=2;l<=len;l++)
for (int i=1;i<=len-l+1;i++){
int j=i+l-1;
for (int k=i;k<j;k++)
for (int h=1;h<=n;h++){
int c1,c2;
c1=to[h][0]-'a'+1;
c2=to[h][1]-'a'+1;
if (f[i][k][c1]==true&&f[k+1][j][c2]==true)
f[i][j][from[h]-'a'+1]=true;
}
}
memset(g,127,sizeof(g));
g[0][0]=0;
for (int i=1;i<=len;i++)
for (int j=i;j<=len;j++)
for (int k=1;k<=26;k++)
if (f[i][j][k]==true){
g[i][j]=1;break;
}//预处理所有能合并成1个字符的区间
for (int l=2;l<=len;l++)
for (int i=1;i<=len-l+1;i++){
int j=i+l-1;
for (int k=i;k<j;k++)
g[i][j]=min(g[i][j],g[i][k]+g[k+1][j]);
}
printf("%d\n",g[1][len]);
return 0;
}
偏偏在最后出现的补充说明
我都不知道我当时考场上怎么就“嘣”的一下想出来了。。。。。。