您的位置:首页 > 其它

12年成都 E 贪心+KMP HDU 4468

2016-06-22 10:56 246 查看
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: