hdu 4333"Revolving Digits"(KMP求字符串最小循环节+拓展KMP)

传送门

 

题意:

  此题意很好理解,便不在此赘述;

题解:

  解题思路:KMP求字符串最小循环节+拓展KMP

  ①首先,根据KMP求字符串最小循环节的算法求出字符串s的最小循环节的长度,记为 k;

  ②根据拓展KMP求出字符串s的nex[]数组,那么对于由第 i 位打头构成的新数b,如何判断其与原数a的大小关系呢?

    1)如果 i%k == 0,那么b == a;

    2)如果 i%k ≠ 0 ,令L=nex[i],那么只需判断s[ i+L ]与s[ L ]的大小关系即可,需要注意的是,如果i+L = len呢?此时又该怎样处理呢?

    方法1:依次判断s[0,1,....] 与 s[ L,L+1,..... ]的关系,直到找出第一个不相等的位置判断其大小;

    方法2:判断 s[ nex[L] ]与s[ L+nex[L] ]的大小关系;

  如果采用方法1,很不幸,会超时,所以,方法2才是行之有效的方法;

  根据题意,此题让求得是不同的数,那么,如何去重呢?

  根据KMP已经求出了k,那么串s得循环周期为 len / k ,那么每种新数必然会重复 len / k次,只要在输出结果上将求出的答案除以 (len/k) 即可;

  还有一点需要注意的是,和原数相同的数,当且仅当只有一个,不论输入任何数,输出1即可;

AC代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 const int maxn=1e6+50;
 6 
 7 char digit[maxn];
 8 int nex[maxn];
 9 
10 int Period()
11 {
12     int len=strlen(digit);
13     nex[0]=-1;
14     nex[1]=0;
15     int cnt=0;
16     int index=2;
17     while(index <= len)
18     {
19         if(digit[index-1] == digit[cnt])
20             nex[index++]=++cnt;
21         else if(cnt != 0)
22             cnt=nex[cnt];
23         else
24             nex[index++]=0;
25     }
26     int k=len;
27     if(len%(len-nex[len]) == 0 && nex[len] != 0)
28         k=len-nex[len];
29     return k;
30 }
31 void getNext()
32 {
33     int len=strlen(digit);
34     nex[0]=len;
35     int j=0;
36     while(j+1 < len && digit[j+1] == digit[j])
37         j++;
38     nex[1]=j;
39     int k=1;
40     for(int i=2;i < len;++i)
41     {
42         int p=k+nex[k]-1;
43         int l=nex[i-k];
44         if(l < p-i+1)
45             nex[i]=l;
46         else
47         {
48             j=max(0,p-i+1);
49             while(i+j < len && digit[i+j] == digit[j])
50                 j++;
51             k=i;
52             nex[i]=j;
53         }
54     }
55 }
56 bool isLess(int i,int j,int len)
57 {
58     if(j == len)//如果j == len
59     {
60         j=nex[i];
61         i=i+nex[i];
62     }
63     return (digit[j]-'0') < (digit[i]-'0');
64 }
65 void Solve()
66 {
67     int k=Period();//KMP求出最小循环节的长度
68     getNext();//拓展KMP求解nex[]
69 
70     int ansL=0;
71     int ansG=0;
72     int len=strlen(digit);
73     for(int i=1;i < len;++i)
74     {
75         int l=nex[i];
76         if(i%k == 0)//与原数相等
77             continue;
78 
79         if(isLess(l,i+l,len))//判断是否小于原数
80             ansL++;
81         else
82             ansG++;
83     }
84     printf(" %d %d %d\n",ansL/(len/k),1,ansG/(len/k));
85 }
86 int main()
87 {
88     int test;
89     scanf("%d",&test);
90     for(int kase=1;kase <= test;++kase)
91     {
92         scanf("%s",digit);
93         printf("Case %d:",kase);
94         Solve();
95     }
96     return 0;
97 }
View Code

  在网上看的其他人写的题解,有个很巧妙的方法:

  将字符串s拷贝一份加入到字符串s中,通过拓展KMP求出nex[]后,对于由第 i 打头构成的新数b:

  1)如果nex[i] > len/2,那么b == a;

  2)判断s[ i+nex[i] ]与s[ nex[i] ]的相对大小;

 


分割线:2019.5.7

省赛临近,重新温习了一下拓展KMP

思路:

  定义串 s , t

  读入数据到 t 串,然后,复制两边 t 串到 s 串;

  以 s 为母串,t 为子串求解ext[]数组;

  遍历一遍串t,对于位置 i ,判断 t[ ext[i] ] 与 s[ i+ext[i] ] 的大小关系;

  前者大,L++,反之, G++;

  输出结果 L / k , 1 , G / k;

  k : 串 t 的循环节;

  之所以 / k 是因为可能有重复的;

代码:

  1 #include<iostream>
  2 #include<cstdio>
  3 #include<cstring>
  4 using namespace std;
  5 const int maxn=2e5+50;
  6 
  7 char s[maxn];
  8 char t[maxn];
  9 
 10 struct KMP
 11 {
 12     int nex[maxn];///数组大小根据题意而定
 13     void getNex(const char *s)
 14     {
 15         int len=strlen(s);
 16         nex[0]=-1;
 17         nex[1]=0;
 18         int cnt=0;
 19         int index=2;
 20         while(index <= len)
 21         {
 22             if(s[index-1] == s[cnt])
 23                 nex[index++]=++cnt;
 24             else if(cnt != 0)
 25                 cnt=nex[cnt];
 26             else
 27                 nex[index++]=0;
 28         }
 29     }
 30     int F(const char *s)///返回串s的循环节
 31     {
 32         getNex(s);
 33         int len=strlen(s);
 34         int res=1;
 35         if(nex[len] != 0 && len%(len-nex[len]) == 0)
 36             res=len/(len-nex[len]);
 37         return res;///最小循环节长度 = len/res
 38     }
 39 }_kmp;
 40 /**
 41     拓展KMP
 42     nex[i]:t[0,...m-1]与t[i,...,m-1]的最长公共前缀
 43     ext[i]:s[i,...n-1]与t[0,...,m-1]的最长公共前缀
 44 */
 45 struct ExtendKMP
 46 {
 47     int nex[maxn];
 48     int ext[maxn];
 49     void getNex(const char *t)///预处理出t串的nex
 50     {
 51         int len=strlen(t);
 52         nex[0]=len;
 53         nex[1]=0;
 54         for(int i=1;i < len && t[i] == t[nex[1]];i++,nex[1]++);
 55         int K=1;
 56         for(int i=2;i < len;++i)
 57         {
 58             int L=nex[i-K];
 59             nex[i]=min(L,max(K+nex[K]-i,0));///K+nex[K]-i 可能小于0,所以两者取max,整体取min
 60             for(int j=i+nex[i];j < len && t[j] == t[nex[i]];j++,nex[i]++);
 61             if(K+nex[K] < i+nex[i])
 62                 K=i;
 63         }
 64     }
 65     void getExtend(const char *s,const char *t)
 66     {
 67         int n=strlen(s);
 68         int m=strlen(t);
 69         ext[0]=0;
 70         for(int i=0;i < n && i < m && s[i] == t[i];i++,ext[0]++);
 71         int K=0;
 72         for(int i=1;i < n;++i)
 73         {
 74             /**
 75                 P=K+ext[K]-1,最右边界
 76                 s[K,...,P] = t[0,.....,P-K]
 77                 s[i,...,P] = t[i-K,...,P-K]
 78                 t[i-K,....,i-K+L-1] = t[0,.......L-1]
 79             */
 80             int L=nex[i-K];
 81             ext[i]=min(L,max(K+ext[K]-i,0));
 82             for(int j=i+ext[i];j < n && ext[i] < m && s[j] == t[ext[i]];j++,ext[i]++);
 83             if(K+ext[K] < i+ext[i])
 84                 K=i;
 85         }
 86     }
 87 }_eKMP;
 88 
 89 void Solve()
 90 {
 91     _eKMP.getNex(t);
 92     _eKMP.getExtend(s,t);
 93 
 94     int L=0,G=0;
 95     int len=strlen(t);
 96     for(int i=1;i < len;++i)
 97     {
 98         int ext=_eKMP.ext[i];
 99         if(ext == len)
100             continue;
101         if(s[i+ext] > t[ext])
102             G++;
103         else
104             L++;
105     }
106     int k=_kmp.F(t);///串t的循环节
107     printf("%d 1 %d\n",L/k,G/k);
108 }
109 int main()
110 {
111     int test;
112     scanf("%d",&test);
113     for(int kase=1;kase <= test;++kase)
114     {
115         scanf("%s",t);
116         strcpy(s,t);
117         strcat(s,t);///拷贝两边t串到s
118         s[strlen(t)<<1]='\0';///可加可不加,最好加上
119         printf("Case %d: ",kase);
120         Solve();
121     }
122     return 0;
123 }
View Code

 

posted @ 2019-03-15 20:14 HHHyacinth 阅读(...) 评论(...) 编辑 收藏