Fork me on GitHub

HDU3746-Cyclic Nacklace

继续跟着邝斌飞刷KMP

HDOJ3746

百度的时候发现题目英语弄错了,项链英文是Necklace,不是Nacklace

 

读完之后我滴妈,上难度了呀

追女生很简单的,体贴温柔,细心照顾,有耐心,请吃饭买包包,不到半年你就知道什么叫打水漂,这不主人公开始追女生了,打算送珍珠手链

题意:

T组数据,0<T<=100

每组数据给你个字符串a~z,长度3<=Len<=100000,视为原始手串,一个字符代表一颗珍珠,

你只能在最左端和最右端添加珍珠,然后最左侧和最右侧相连,使得循环计数≥2,

例如题目中从最显眼的蓝色开始,顺时针数9个为一个循环计数,又到了蓝色再数9个(都是跟之前一模一样的珍珠),至此有两个循环计数,问你对于给定的字符串,最少添加多少个珍珠符合题意

 

思考:

首先想个例子:abcabc

去掉左右的话是  bcab

我们根据上帝视角,知道应该左侧填 a 右侧填 c,

然后连接的话是

         b   

              c

              a 

         b

看成一个环,首是第二行的 a 然后顺时针 bc ,一个循环计数结束。

然后第三行的 a 顺时针 bc ,发现尾巴 c 在首a下面,实际首尾连接也必然如此,首尾在一起。

那么一开始只有 bcab,你左加a右加 c,是否可以移动下,左啥也不加,只在右即尾巴处加 ca ,使得串由于原来的两个 abc,变为两个 bca

 

随便画了几个情况

对于图1,abcde abcde那个, 这是题意要求的,发现 next 数组是 -1 0 0 0 0 0 1 2 3 4,循环是5,循环计数是2,没看出啥来,

再看图2,abc abc abc,去掉左侧 a 右侧 c ,bc abc ab ,显然按照我的思路是直接最右边填 ca ,但添加后的 bca bca bca 的 next 是,-1 0 0 0 1 2 3 1 0,这尼玛更离谱,也没看出啥来,我还以为会是像第一个那样,1234这种递增的 next 值有啥突破口,那如果左侧也加呢?变成正常的 abcabcabc ,发现 next 是一样的,想想也是,next 与具体数字无关,只与字符串哪位跟哪位相等这种内在性质有关系。也更进一步懂了,确实在头加,和只在尾巴加是一样的,因为结果 next 是一样的,解题去求最少添加几个其实也是构造出这个最后 next 的结果关系。跟具体字符没关系。

划掉是因为 next 搞错了,无误以为比如 ababa ,最后一位的 next 是 0 ,误以为是看首位 a 和倒 数第二位 b 不等就是 0 了,其实 next 是 ab ,是 2 。bca bca bca 的 next 是:-1 0 0 0 1 2 3 4 5。

拉鸡巴倒,再看个更普遍或者说更***钻的吧,然后来大胆想一下,即图3,就是让循环里有一样的,比如bcabd,这里两个b,那两个循环计数为2,即串为 bcabd bcabd ,如果我去掉左侧 b 右侧 bd ,剩下 cabdbca ,发现 next 是 -1 0 0 0 0 0 1,出现了 1 ,我再去想,相同的两个b那附近都没出现 1 ,在这出现 1 了,是不是说明一个循环手串就是这么长?即填上 bdb , cabdbca bdb,变为 cabdb cabdb

这个例子的好处在于,同一个循环手串里,有相同的字符,这样就使得例子更普遍性,或者说更***钻,这样出来的什么规律会觉得不是巧合,便于往这个规律结果上想

发现代入上两个图也是。

我靠?那是不是直接 next 为 1 ,我就直接可以得出最少该填的珍珠数量了?(仔细想想这不是巧合,就是这样的,因为出现 1 了就代表,跟头开始重复,开始重来了)

不能这么简单吧?构造了一个,感觉不对

abacad abacad ,第一个 next 为 1 的位置是第4位,c那,但并不是一个循环手串结束啊。

next :-1 0 0 1 0 1 0 1 0 1 0 1。发现又想错了,next 是 -1 0 0 1 0 1 , 0 1 2 3 4 5 。

想一下,目前串是 abacad abacad ,next 为 -1 0 0 1 0 1 , 0 1 2 3 4 5 。

那这么思考下:

如果串是 abacad abaca 少一位,需要你填一位,next 为  -1 0 0 1 0 1 , 0 1 2 3 4 ,末尾是 4 ,想想可以怎么构造?总共 11 个珍珠,最后一位是 4 ,那我填一个就是偶数了,构成循环计数为2.

再试试,如果串是 abacad abac 少两位,需要填两位,next 为 -1 0 0 1 0 1 , 0 1 2 3 ,末尾是3,总共10个珍珠,0123这四个代表为一个计数循环,前面-1 00101这6个为一个计数循环,那总共是补两个。

再试试,如果串是 abacad aba 少三位,需要填三位,next 为 -1 0 0 1 0 1 , 0 1 2 ,根据上面的012这三个构成了和前面一样的串,-1 00101这6个构成一个串,那需要填 6-3,三个

再试试,如果串是 abacad ab 少四位,需要填四位,next 为 -1 0 0 1 0 1 ,0 1 , 末尾是 1 同理。填4个.

再试试,如果串是 abacad a,少5位,next 为 -1 0 0 1 0 1 , 0 , 末尾是0,填5个。

再试试,如果串是 abacad ,next 是 -1 0 0 1 0 1 , 填6个,abacad

再试试,如果串是 abaca,next 是 -1 0 0 1 0 , 填3个,只需要 bac 就好了

再试试,如果串是 abac ,next 是 -1 0 0 1 , 填4个,abac

再试试,如果串是 aba ,next 是 -1 0 0 , 填1个,b

再试试,如果串是 ab ,next 是 -1 0 , 填2个,ab

再试试,如果串是 a ,next 是 -1 , 填1个,a

观察规律的话其实-1就没啥用了,-1是给 next 数组赋值做一个加1得0用的,现在观察把-1视为0就好了,还有,祖师爷里说的KMP优化也不需要了,KMP优化那个用于跟主串匹配,直接拖动到一次到位的位置,如果加上那个剪枝,abacad abacad 的 next 就是 -1 0 -1 1 -1 1 -1 0 -1 1 -1 1,这一丁点规律都没有

那我感觉有这样一个规律:

首先对于字符串里的1其实是没用的,比如 abac 的 next 为 -1 0 0 1,这个 1 只是说明第一个 a 和第三个 a 一样,跟整个循环串 abac 是否结束,没一点关系,所以我觉得应该多看一位,比如 abac 应该看 next[4] 是多少,是0,那我觉得只要是多看的这位,为0就可以直接确定,前面有多少位就填对少位,填4位。然后如果多看的这位为1,比如aba,next[3]是1,应该填1位,那就构造一下,循环串为3-1位,已经有了1位,该再填1位。将所有举过的例子都套一遍发现都成立

 

但还有个事,现在这是需要添加珍珠的情况,如果不需要添加珍珠怎么判断?或者说怎么确定已经满足循环计数大于1了? 我加了个判断,

if(ne[len]==0)
    cout<<len<<endl;
if(ne[len]!=0){
    if(len-ne[len]-ne[len]<0)
        cout<<0<<endl;
    else
        cout<<len-ne[len]-ne[len]<<endl;
}

如果多看的这位,next 是 0 ,就相当于完整串结束,需要完完全全再来一个len。

如果多看的这位,next 不是 0 ,比如 abca,多看位的 next[4] 是 1 ,len 是 4 ,那 len -next[4] 就是一个完整循环串的长度,next[4] 的这个 1 ,意思是记录了除了一趟完整的循环串外,再来的一次循环计数里,来了多少个字符,那 1 表示来了一个字符,想想也是,abc 为一个完整循环串,后面的 a 就是来的一个,还需要再填几个呢?,那只需要减去这个就好了,即 len - next[4] -next[4] ,

如果多看的这位,next 不是 0 ,比如 abcabc,多看的位 next[6] 是 3,循环串长度是 len - next[6] 为 3 ,那还需要填几个呢?再减去 next[6] 是 0 ,即不需要填了

提交下但WA了,

我想了两个例子:

0、

aaa 长度是 3 ,next[3] 是 2 ,3-2是1,应该代表完整循环串的长度,即 1 ,再减去next[3] 就是-1 了,所以来了个小于0的特判,即不需要再填了。这个属于歪打正着。正好是答案,但其实 next[3] 是 2,应该记录的是来了完整串中的几个字符,可是完整串是 1 啊,怎么会来了两个,这里其实因为循环计数是3了,之前 len - next[len] - next[len] 是针对循环计数为2想的。那再举一个例子

1、

abcabcab 输出 len - next[len] - next[len] 是 8-next[8]-next[8] ,等于 -2 ,根据小于 0 就输出 0 表示不用填了,可是不对啊,显然应该填 1 个 c ,错误在于 next[8] 很离谱的是 5 ,即为 abcab ,重复的也给算上了,同理有 abaaba 应该是输出  0  ,确实输出 0 ,同理有 ababa 应该输出 1 ,却输出 0 ,

那直接改下判断逻辑:对于 ababa ,123 == 345,那 12 == 34 ,len - next[5] 的这个 2 就是循环串长度,原来逻辑是再减 next[5] ,即再减 3 ,next应该记录的是来了几个?3超过了本来的循环串长度,证明一定有一个是重复的,那我想一下能不能用%呢?这个就算AC也感觉有点蒙。

即  (len-ne[len]) - (ne[len] % (len-ne[len])) ,  (len-ne[len])  是 循环串长度,(虽然不合逻辑,但是结论就是这样的), (ne[len] %一下就代表来了几个循环串中的字符,再用循环串长度减一下就是答案。感觉有点说不通,先提交下试试吧。

测了一个 aaa 发现 ,由于2%1是0, (len-ne[len]) - (ne[len] % (len-ne[len])) 答案是 1 了,那就再打个补丁,能模掉为0,表示来了一遍了不需要你再填数字了

提交下

卧槽??居然AC了,好离谱啊,我思维感觉好混乱,属于想出漏洞就到处打补丁,没从根本上解决解题逻辑,这也能AC?

AC代码 —— 莫名其妙就AC了,最后的判断输出逻辑没法说服我自己

复制代码
 1 #include<stdio.h>
 2 #include<string.h>
 3 #include<iostream>
 4 using namespace std;
 5 int T;
 6 int ne[100001+1];
 7 int main()
 8 {
 9     std::ios::sync_with_stdio(false);//不加这个TLE,加了WA了
10     cin>>T;
11     string str;
12     while(T--){
13         cin>>str;
14         int len=str.length();
15 
16         //求next
17         ne[0]=-1;
18         int j=0;
19         int k=-1;
20         int qqq=1;
21 //        for(int i=0;i<len-1;i++){//多算一位,一开始写错了,回顾了下:https://www.cnblogs.com/gerjcs/p/18605693那个图
22         while(j<len){
23             if(k==-1 || str[j]==str[k]){
24                 j++;
25                 k++;
26                 ne[j]=k;
27             }
28             else
29                 k=ne[k];
30 
31         }
32         ne[0]=0;
33 //        for(int i=0;i<=len;i++)
34 //            cout<<ne[i]<<" ";
35 //        cout<<endl;
36 
37 //    cout<<"#"<<ne[len]<<" "<<len-ne[len]<<" "<<ne[len] % (len-ne[len])<<endl;
38 
39         if(ne[len]==0)
40             cout<<len<<endl;
41         if(ne[len]!=0){
42             if(ne[len] % (len-ne[len]) == 0)
43                 cout<<0<<endl;
44             else
45                 cout<<(len-ne[len]) - (ne[len] % (len-ne[len]))<<endl;
46         }
47     }
48 }
复制代码

 

看看别人咋写的,博客, 他说“题目有hint(不是hind233333)”,???他妈的hint搁哪呢???hdoj也没有这个提示啊。

妈逼的我都AC了却看不懂他们的博客:参考1参考2参考3参考4

不如说懒得看。

算了自己想吧,也是奇葩,自己AC的题现在要倒退着想,然后来说服自己。

刷这么久题,头一次遇到这种

 

那就用一个比较***钻的例子吧,让循环计数为5,不然的话计数为1或者2总觉得哪里有数字巧合。因为0 1 2这种数字太常见了,起码上升到3、4、5以上才行。

说个插曲

插曲开始

无意间测试又发现问题了,对于freopen,文本里最后如果没有回车,他真的会自己自动的给你补全。然后发现无论加不加回车都tm会自己给你补全,代码如下

复制代码
 1 #include<stdio.h>
 2 #include<string.h>
 3 #include<iostream>
 4 using namespace std;
 5 int T;
 6 int ne[100001+1];
 7 int main()
 8 {
 9     freopen("zhishu.txt","r",stdin);
10     std::ios::sync_with_stdio(false);//不加这个TLE,加了WA了
11     cin>>T;
12     string str;
13     while(T--){
14         cin>>str;
15         int len=str.length();
16 
17         //求next
18         ne[0]=-1;
19         int j=0;
20         int k=-1;
21         int qqq=1;
22         while(j<len){
23             if(k==-1 || str[j]==str[k]){
24                 j++;
25                 k++;
26                 ne[j]=k;
27             }
28             else
29                 k=ne[k];
30 
31         }
32         ne[0]=0;
33 
34         cout<<len<<" "<<ne[len]<<" "<<len-ne[len]<<endl;
35         if(ne[len]==0)
36             cout<<len<<endl;
37         if(ne[len]!=0){
38             if(ne[len] % (len-ne[len]) == 0)
39                 cout<<0<<endl;
40             else
41                 cout<<(len-ne[len]) - (ne[len] % (len-ne[len]))<<endl;
42         }
43     }
44 }
View Code
复制代码

txt里是

333
abcabcabcabcab

有没有回车都会自动补全。GPT也没解决,搁置吧,之前遇到过遇到过也没解决。

插曲结束

两个例子就能说明问题:

一个是不重叠,不咬住尾巴的例子:abcabcabcabcab ,总长度是14,循环串是 abc ,循环计数是 4 ,next[14] 是 11 ,即

abcabcabcabcab 

abcabcabcabcab 

那么发现14-11等于3正好可以理解为一个循环串的长度,为啥?他也不是循环串 abc 啊。

这么想,一块蛋糕切掉了一大半,我记作大Q,剩下的一点叫做大S,那么你上面 next[14] 是 11 ,代表我后缀的最后几个多出来的并不能组成完整循环串的字母(ab)连上前面的m个完整循环串 , 一定可以和 前缀里包含着m个完整循环串的字母,再多几个后缀的最后几个多出来的并不能组成完整循环串字母(ab)),就是后缀的最后几个字母 匹配,这就是next[14],

具体就是 最末尾的后缀的最后几个多出来的并不能组成完整循环串的字母ab 连上前面的3个完整循环串abcabcabc一定可以和 前缀里包含着3个完整循环串的字母abcabcabc,再多几个和后缀的最后几个多出来的并不能组成完整循环串的字母一样的字母ab,匹配,即 前缀的 abcabcabcab 和 后缀的 abcabcabcab 匹配。

总共长度是14,next[14]为11,可以理解为一个完整蛋糕是abc,有3个完整蛋糕,再多切一个完整蛋糕中的大Q部分,即 ab ,那剩下的 c 就是大S部分,再补个ab,这个也是大Q部分,所以相当于 14-11就是 大Q + 大S 正好是一个完整的循环串。

再加深理解举个例子:abcabcabcabca ,长度是13,next[13]是10,3个abc蛋糕+一个单独的a,这个a算作切下来的大Q,后面的字母有bca,(bca啥意思,里面一定有一个和切掉的部分:大Q,一模一样的字母ab,因为前后缀要匹配啊,之间还会有个切完剩下的字母大S,正好组成了完整循环串),其中bc是上一波切剩下的大S,又多了个头a,正好组成完整循环串。有点像贪吃蛇一样吃了个尾巴补了个头的意思。目前 len - next[len] 就理解了是代表一个完整循环串长度的意思

另一个是重叠咬住尾巴的例子:abababababa ,总长度是11, 已有的循环计数是 5 ,循环串是 ab ,咬住尾巴是指ab会被KMP搞成类似于aba的样子,即如果只有ababa的话,多看位的next,即 next[5] 为 3,aba 。知道这个意思就行,来看总长度为11的这个例子,多看的那位的next,即 next[11] 是 9 ,即 ababababa ,如下:

abababababa 

abababababa 

怎么理解这个 9 ,按照上一个不要尾巴的例子来想,虽然咬住了尾巴,使得真实串从 ab 变成了 aba ,但他前后缀里所有的完整循环串也是按照这个来的,

就比如 ababa 那个,长度是 6 ,next[6]是 3 ,前 aba 和后 aba ,但我们依旧可以把他当成循环串为我们的 ab ,那就好解释了,aba 表示一个蛋糕整体 ab ,加切一刀得来的 a ,记为大Q,由于前缀必须和后缀一样,所以末尾一定有几个字母是这个切一刀来的大Q,那之间就一定是剩下的大S了,所以有大S + 大Q,即 len - next[len] 就可以表示一个循环串的长度。

回到这个例子,9也是一样的,前面一定有n个整体的循环串,外加一个从整体循环串切一刀来的,或者说最末尾的几个无法组成完整循环串的字母,称之为大Q,这里是倒数第三个字母 a ,那由于 前缀 == 后缀 ,所以整个串最末尾一定会有这个大Q再次出现然后就前缀最末尾 和 后缀最末尾,这之间一定有个前缀切一刀剩下的大S,所以有大S + 大Q,即 len - next[len] 为完整循环串长度。

插一嘴,如果理解不了,为啥之间一定有前缀切一刀剩下的大S,你瞅下面的图

位置:0  1  2  3  4  5  6  7  8  9  A

           a  b  a  b  a  b  a  b  a  b  a 

           a  b  a  b  a  b  a  b  a  b  a

可以看出给的串的 01 23 45 67 8位 == 23 45 67 89 A位,(正好01一组循环串,23一组循环串,8和A单独切的大Q)

           a  b  a  b  a  b  a  b  a  b  a  前缀

   a  b  a  b  a  b  a  b  a  b  a          后缀

那其实可以看出:

01 23 45 67 8

23 45 67 89 A

从第二个串可以看出,切掉的8后一定是个9,即完整串剩下的大S,不然也不会匹配上。

至此 len - next[len] 为循环串长度解释通了

再回一下,next[len] 代表啥?代表有若干个完整的循环串,或许可能再多一个从一个循环串切下来的大Q,那我模%一下循环串长度( len - next[len] ),啥意思?不知道的话你想下,如果是除法啥意思?用 next[len] / 循环串长度 代表有多少个完整的循环串,那模%就是剩下的多余的东西,即切下来的大Q,进一步说:

如果是 0,    代表啥也没切,代表正好就是完整的循环串,不多什么,那啥也不用填了。

如果不是0 ,代表切的大Q,用一个完整的循环串长度, len - next[len]  减 一下就是需要填的字符个数。

好了那现在清楚了,我们判断的时候:

如果 next[len] 是 0 ,表示没有完整的循环串,那既然结束输入了,输入的就是如此,我只能把你当成循环串了,再来你 len 这么多。

如果 next[len] 不是 0 ,这才是我们上面说的,“代表有若干个完整的循环串,或许可能再多一个从一个循环串切下来的大Q”,这时候才区分大Q是不是0

 

啊有点想通了,但是码字解释好累啊

 

最后,这题是AC后才想通的,但也只是有些许了解,当时全凭借做题感觉,思路一下就有了,如果想复现,考场上就还是稳定发挥吧,其实不写博客想的更深刻更快速更透彻

 

没听懂的留言,加微信给你语音解答,希望帮助到后来的人,至少别像我一样,走了太多太多弯路,他奶奶的,如今一无是处穷途末路。像个傻逼废物垃圾废人。acm纯tm网瘾,一点用没有。

 

###:妈逼的不知道咋回事百度那个搜索不知道是不是总更新网页html一些东西,ADblock拦截又tm不好使了,还得重新拦截。不知道为啥拦截的元素过滤规则一样,就是需要重新把ADblock的高级拦截规则里,对应开关关闭再开启一下,或者重新拦截一下。真恶心搜索框跟下面热搜框一起的,要关就都关了艹

复制代码
baidu.com###lg
baidu.com##.s-p-top
baidu.com##.s-top-right.s-isindex-wrap

真JB绝了,这三条在一起管baidu.com上面那个图片的,不小心给关了,试了好久

筛选出baidu.com的,复制到记事本,然后全删掉,5个5个一填
View Code
复制代码

###:感觉写博客好浪费时间,不想写博客了,强迫症好挣扎

复制代码
写博客好费时间,怕回忆不起来思路历程。所以想到一点就写一点,唉

每天都是读完题第二天或者走路或者。感觉现场考试时间会不够

还有每天都是饥饿状态
身体都是疲惫
脑子脑动力跟不上。这样的状态下刷题

体力比赛:


骑车也是你们比赛前提供的吃住对我是天堂般的唯一一顿吃饱饭,超常发挥
你们火车休息不好
而我是烂路骑车骑一宿到比赛起点出发地出发点啊

打牌也是,无法复盘
View Code
复制代码

###:vjudge讨论区都是啥水平啊?用cin会WA?纯属放屁,WA不WA跟cin还是scanf有个JB关系啊,一群垃圾傻狗。根据我的经验,这篇博客博客里说过,一个char一个char的读取会因为回车和空格导致问题,但又不是不可控,你处理好不就行了,但那也是scanf的啊,cin不吃回车不吃空格,就算出错也应该是scanf导致出错啊。

再回顾一遍:

 说明cin不吃回车不吃空格,会在缓冲区里存着,scanf读空格,试了下也读回车

###:注意:next表述更显而易见让人知道是 KMP 的 next 数组,只是代码里用的是ne,不能用next,next是C++库里的关键字。实验室

posted @   GerJCS  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
· Manus的开源复刻OpenManus初探
点击右上角即可分享
微信分享提示