[日常训练] 压缩

来源:互联网 发布:thinkphp分销系统源码 编辑:程序博客网 时间:2024/06/08 19:36

PS:由于编辑器原因以下用“#”代替“$”

【题目描述】

巨大的文本往往令人头疼,特别是文本内容有大量重复的情况下,巨大的文本不便于运输和阅读,于是我们提出了NOIP(Nonsense Obivous Index Pattern(荒谬的显然索引法)),一种“有效的”压缩文本的方法。
NOIP压缩后的格式很特别,一个文本压缩后由若干个单元组成,每个单元由3部分组成:1.正文(小写字母组成的字符串)2.若干个“*”,表示正文的又重复了几次3.单元的结尾符号“#”
比如,NOIP压缩后的文本hello**#yes#no****#的含义是“hello”重复3次,yes重复1次,no重复5次,解压后就变成hellohellohelloyesnonononono。
显然,对于同一文本,压缩后的表示方法不唯一,但是为了方便,我们要求你采用压缩后字符串最短的压缩方法,如果有多种压缩方法,只需输出任意一种。(special judge)

【输入格式】

一个字符串,只含小写字母,表示原来的文本。

【输出格式】

一个字符串,表示一种最短的压缩后文本。(special judge)

【输入样例1】

aaaa

【输出样例1】

aa*#

【样例解释1】

除此外还有多种压缩方法,但是长度都比样例输出长,以下列举其中几种:
a***#
aaaa#
a**#a#

【样例输入2】

hellohellohelloyesnonononono

【样例输出2】

hello**#yes#no****#

【数据范围】

记len为读入的字符串长度。
20%的数据,len<=10
70%的数据,len<=200
100%的数据,len<=2000

【分析】字符串哈希/KMP + DP

  • f[i]表示处理到第i位时当前最短的压缩文本长度,则转移显然为f[i]=f[j1]+len[j][i](其中len[j][i]表示从第j位到i位最短的压缩文本长度)
  • 考虑到时间复杂度,我们需要预处理出len[j][i],可用字符串哈希或KMP解决
  • 但最后不是输出最短长度而是压缩后的文本,我们记录每次转移到i的最优决策j1,并在预处理len[j][i]时同时预处理出第j位到第i位压缩文本最短时循环节的长度和个数,最后沿着最优决策倒着找回去,也就解决了输出的问题

【代码】

#include <iostream>#include <cstdio>#include <cstring>using namespace std;typedef unsigned long long ull;const int Maxn = 0x3f3f3f3f;const int N = 2005;ull p[N], h[N]; char s[N];   int len[N][N], apr[N][N], str[N][N], ret[N], stk[N], f[N];int tmp, top, n;inline ull Hash(const int &x, const int &y){    return h[y] - h[x - 1] * p[y - x + 1];}int main(){    freopen("compress.in", "r", stdin);    freopen("compress.out", "w", stdout);    memset(len, Maxn, sizeof(len));    memset(f, Maxn, sizeof(f));    p[0] = 1; scanf("%s", s + 1); n = strlen(s + 1);    for (int i = 1; i <= n; ++i) p[i] = p[i - 1] * 26;    for (int i = 1; i <= n; ++i)     h[i] = h[i - 1] * 26 + s[i] - 'a';    for (int k = 1; k <= n; ++k)      for (int i = 1; i <= n; ++i)      for (int j = i; j + k - 1 <= n; j += k)      {        if (j > i && Hash(j - k, j - 1) != Hash(j, j + k - 1)) break;        tmp = k + (j - i) / k + 1; int ed = j + k - 1;        if (len[i][ed] > tmp)         {            len[i][ed] = tmp; str[i][ed] = k;            apr[i][ed] = (j - i) / k;            // str[i][ed],apr[i][ed]分别表示            // 当压缩文本最短时,第i位到第ed位循环节的长度和个数         }      }    f[0] = 0; f[1] = 2;    for (int i = 2; i <= n; ++i)     for (int j = 1; j <= i; ++j)     {        tmp = f[j - 1] + len[j][i];        if (f[i] > tmp) f[i] = tmp, ret[i] = j - 1;     }    for (int i = n; i; i = ret[i]) stk[++top] = i;    // stk[top…1]表示按位置顺序的最优决策     tmp = 1;    for (int i = top; i >= 1; --i)    {        for (int j = tmp; j <= tmp + str[tmp][stk[i]] - 1; ++j) putchar(s[j]);        for (int j = 1; j <= apr[tmp][stk[i]]; ++j) putchar('*');        putchar('$'); tmp = stk[i] + 1;    }      fclose(stdin); fclose(stdout);    return 0;}
原创粉丝点击