[关闭]
@huangdanning 2019-03-15T15:50:12.000000Z 字数 2137 阅读 743

题解 洛谷P3502 [POI2010]CHO-Hamsters

编程 题解 洛谷 POI


首先登出原题位置


好了,进入正题(废话)

本来这题是联赛模拟的一道T3,然而在联赛前一天被我找到了原题...

话不多说讲做* _ *

首先

互不包含是一个很重要的前提,这说明n个字符串在答案串S中的m次出现一定会依次出现(指的是次出现的首位置一定会不同,但m次出现可能会存在重叠)

那么我们预先处理出来每个串接在另一个串的后面所需要新添加的字符数量,记为dis[i][j]。这里涉及的的字符串匹配,官方正解给出的是AC自动机然而我这个小蒟蒻不会呀,所以就用的是KMP算法,时间复杂度是,把式子化一下会发现复杂度就是,所以还是能过的。

如果有了dis[i][j],那么应该不难想到一个的DP式。我们设dp[t][i]表示总串中已经出现了t个字符串,且最后一个出现的串是i号串的最小代价,代码如下:

  1. for (int i=1;i<=n;i++)
  2. dp[1][i]=len[i];
  3. for (int t=2;t<=m;t++)
  4. for (int i=1;i<=n;i++)
  5. for (int j=1;j<=n;j++)
  6. dp[t][i]=min(dp[t][i],dp[t-1][j]+dis[j][i]);

最终的答案就是

然而我们发现很大,怎么办呢/ - ^ - /

上面的那个式子是不是有点眼熟?有点像

我们可以这样转化一下模型:把每个字符视为一个点,一次转移相当于从一个点走到另一个点,最终的答案就是问你总共走m次的最小距离。边界情况:第一个串的代价总是等于串长,所以我们新建一个0号点,另,那么在经过次转移之后,最终答案就是

这样,每一次转移就相当于跑一次怎么办?矩阵加速优化!

至此这题应该就讲完了,具体代码实现应该还是不难的^-^

  1. #include<cstdio>
  2. #include<cstring>
  3. #include<algorithm>
  4. using namespace std;
  5. #define ll long long
  6. const int _ = 205;
  7. const ll INF = 1e18;
  8. char s[_][100005];
  9. int n,m,len[_],next[_][100005];
  10. ll ans=INF;
  11. struct matrix
  12. {
  13. ll f[205][205];
  14. matrix operator * (matrix &b)
  15. {
  16. matrix c;
  17. memset(c.f,63,sizeof(c.f));
  18. for (int i=0;i<=n;i++)
  19. for (int j=0;j<=n;j++)
  20. for (int k=0;k<=n;k++)
  21. c.f[i][k]=min(c.f[i][k],f[i][j]+b.f[j][k]);//一遍Floyed,和矩阵乘法类似但有不同
  22. return c;
  23. }
  24. }Tra,Ans;//状态矩阵Ans,转移矩阵Tra
  25. int main()
  26. {
  27. //freopen("hamsters.in","r",stdin);
  28. //freopen("hamsters.out","w",stdout);
  29. scanf("%d %d\n",&n,&m);
  30. for (int i=1;i<=n;i++)
  31. scanf("%s",s[i]+1),len[i]=strlen(s[i]+1);
  32. for (int t=1;t<=n;t++)
  33. {
  34. int j=0;
  35. for (int i=2;i<=len[t];i++)
  36. {
  37. while (j&&s[t][j+1]!=s[t][i]) j=next[t][j];//预处理next数组
  38. if (s[t][j+1]==s[t][i]) j++;
  39. next[t][i]=j;
  40. }
  41. }
  42. Tra.f[0][0]=INF;//边界情况
  43. for (int x=1;x<=n;x++)
  44. {
  45. Tra.f[0][x]=len[x];Tra.f[x][0]=INF;//边界情况
  46. for (int y=1;y<=n;y++)
  47. {
  48. int j=0;
  49. for (int i=2;i<=len[x];i++)
  50. {
  51. while (j&&s[y][j+1]!=s[x][i]) j=next[y][j];
  52. if (s[y][j+1]==s[x][i]) j++;
  53. if (i==len[x]) Tra.f[x][y]=len[y]-j;
  54. }
  55. }
  56. }
  57. for (int i=0;i<=n;i++)
  58. for (int j=0;j<=n;j++)
  59. Ans.f[i][j]=Tra.f[i][j];
  60. for (m--;m;m>>=1)//Ans的初始状态就已经有一次转移了,所以m--
  61. {
  62. if (m&1) Ans=Ans*Tra;
  63. Tra=Tra*Tra;
  64. }
  65. for (int i=1;i<=n;i++)
  66. ans=min(ans,Ans.f[0][i]);
  67. return printf("%lld\n",ans),0
  68. }
  69. //完结撒花!!!

洛谷千万人,守信第一人,复制原代码,提交两行泪!(有防抄袭

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注