[日常训练] 压缩

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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值