12年成都 E 贪心+KMP HDU 4468

12年成都 E 贪心+KMP HDU 4468

重要的事情说在前面,以后KMP照这样写。

赛中的时候过的人不多,也没有什么具体的思路。发现问题可以转化为最短后缀,使得前面的字符串都是它的子串。甚至想着能不能枚举后缀,O(1)或者log的查询。然后就走入死角了。
实际上还是字符串的题目做的不够多。如果是倒序遍历的话,那查询的字符串操作也应该是倒序的,不存在一个倒序一个正序之说。再者,要检测之前是否为其子串,最坏情况下需要把整个字符串都遍历一遍,也不存在可以log查询的方法。
正解是贪心+KMP。过程就是如何构造最小的明文s。存储一个明文s,在向后遍历的过程中,如果当前节点i不能与字符串匹配,则从上一次记录的last指针处,把密文中[last,i]都加入明文。最后输出答案时,由于明文一定为后缀,所以之前产生明文可能出现最后后缀匹配完全、但是明文没有遍历完全的情况,这时候要把整个后缀都加入明文,答案为(密文长度-last指针+明文长度)。
那么,问题来了。

1. 为什么贪心的方法是这样,而不是把每一个不能匹配的字符单独加入明文呢?
对于字符串abcad,第四个a能与第一个a匹配,d不能与任何一个匹配。假设只是单独把d加入明文,则明文为abcd,然后就发现这样是不能构成字符串abcad的。证毕。
2. 为什么这样贪心是正确的?
没有很明确的证明方法,假设对于字符串A-B-C(表示子串而不是字母),那么如果A和B不匹配,肯定要把B整个加进去才能合法,不存在不加B就能合法的情况。换句话说,遇到不合法的子串,你可以选择多加,但是这个子串一定要加入明文,而且多加的部分设为B,必要部分为A,如果后面出现A不匹配的情况,多加的B也是没有任何作用的。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <string>
#include <algorithm>
#include <iostream>
using namespace std;
const int MAXN = 1e5 + 5;
char s[MAXN], t[MAXN];
int f[MAXN], cnt;
void add(int l, int r)
{
    for(int i = l, j = f[cnt - 1] ; i <= r ; i++) {
        t[cnt++] = s[i];
        while(j != -1 && t[j + 1] != s[i]) j = f[j];
        if(t[j + 1] == s[i]) j++;
        f[cnt - 1] = j;
    }
}
int main()
{
    int cas = 0;
    while(scanf("%s", s) != EOF) {
        int n = strlen(s);
        for(int i = 0 ; i < n ; i++) t[i] = '#';
        cnt = 0;
        t[cnt++] = s[0], f[cnt - 1] = -1;
        int last = 1;
        for(int i = 0 , j = -1 ; i < n ; i++) {
            while(j != -1 && t[j + 1] != s[i]) j = f[j];
            if(t[j + 1] == s[i]) j++;
            if(j == cnt - 1) {
                last = i + 1;
            }
            else if(j == -1){
                add(last, i);
                last = i + 1;
            }
//            printf("i = %d, j = %d, last = %d, cnt = %d, s[i] = %c, s[j + 1] = %c\n", i, j, last, cnt, s[i], t[j + 1]);
//            printf("f ");
//            for(int k = 0 ; k < cnt ; k++) printf("%d ", f[k]);
//            printf("\n");
        }
//        printf("last = %d\n", last);
        printf("Case %d: %d\n", ++cas, n + cnt - last);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值