1 条题解
-
0
自动搬运
来自洛谷,原作者为

1cm
**搬运于
2025-08-24 22:25:19,当前版本为作者最后更新于2025-06-02 14:17:52,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
解题心路:
第一眼看题的时候有点疑惑,明明都是独立随机的符号,为什么连续序列的可能性是不一样的呢?
仔细一想发现可能是因为有的连续序列在总序列中出现多次,但是只能算一次。
怎么解决呢?直接容斥硬算肯定不行,可以分析一下性质。
由样例二可以看到,好像含有更多连续重复的符号、首尾相似的序列,其出现可能性更低,毕竟它们可以重叠起来出现在总序列中,增加了重复计算的次数,扣除之后当然概率更低。
这样类似循环节的性质(注意!对于此处的“循环节”而言,不同点在于字符串长度不一定要是循环节的整数倍,比如
abc也可以是abcabca的循环节。)能自然而然地引发联想:是不是循环节越短、越多,连续序列重叠的可能就越大,因而预测的可能性就更低呢?验证猜想:
我们不妨用程序手模一下:(用
012表示三个符号)#include<bits/stdc++.h> using namespace std; #define N 531441//3^n #define M 729//3^m #define n 12 #define m 6 struct s{ int num,lm[m]; int id; }a[M]; int ln[N][n]; bool cmp(s a,s b){ if(a.num==b.num){ return a.id<b.id; } return a.num>b.num; } int main(){ freopen("x.out","w",stdout); for(int i=0;i<N;i++){ int I=i; for(int j=0;j<n;j++){ ln[i][j]=I%3; I/=3; } } for(int i=0;i<M;i++){ a[i].id=i; int I=i; for(int j=0;j<m;j++){ a[i].lm[j]=I%3; I/=3; } } for(int i=0;i<N;i++){ for(int j=0;j<M;j++){ for(int k=0;k+m-1<n;k++){ bool bbb=1; for(int l=0;l<m;l++){ if(ln[i][k+l]!=a[j].lm[l]){ bbb=0; break; } } if(bbb){ a[j].num++; break; } } } } sort(a,a+M,cmp); for(int i=0;i<M;i++){ for(int j=0;j<m;j++){ cout<<a[i].lm[j]; } cout<<' '; cout<<a[i].num<<endl; } }当总序列长度足够大( 是一个超级大的数),连续序列长度为 时,输出节选如下:
- 第一列为连续序列举例,
- 第二列为出现次数(扣除重复)
- 第三列为循环节(未间隔)
100000 5102:6 010000 5096:56 100010 5075:46 001000 5069:456 100100 4995:36 010010 4989:356 101010 4698:246 000000 3645:123456可以看到,我们的猜想有点道理。
如果把各个例子的循环节用 补全到六位数,依次便为
600000/560000/460000/456000/360000/356000/246000 123456,数字越小,预测可能性越小。那么如果总序列长度不够大呢?比如为 ? 结果如下:
100000 405:6 010000 405:56 100010 405:46 001000 404:456 100100 399:36 010010 399:356 101010 378:246 000000 297:123456出现次数重复了,似乎不是非常适用。
但是考虑到总序列长度为 的情况下,两个长度为 的连续序列最多只有 位重叠,所以长度大于等于 的循环节是无用的,因而将其去掉。(留下各个数字都含有的 不影响结果,而且便于排序)
100000 405:6 100010 405:46 100100 399:36 101010 378:246 000000 297:12346好啊,很好啊。
至此我们打通了思路(结论显然正确,严格证明移步其他题解,
绝对不是因为本蒟蒻不会证),接下来开始代码实现。具体算法:
对于循环节的处理,我们利用 KMP 的思路,对每一个连续序列求出失配数组,然后从连续序列的末尾开始不断跳失配。
过程中所得的下标用其长度 减去的结果恰好就是我们所需的各个循环节。(注意长度大于 的循环节不应记录)
一张简易的的原理图:( 为过程中下标变化)
得到每个连续序列的循环节后,按照- 首位更大
- 数量更多
- 输入顺序靠前
对其排序即可得到答案。
代码实现:
#include<bits/stdc++.h> #define N 1000010 #define M 15 #define LEN 100010 using namespace std; struct STR{ char s[LEN]; int id,numc,cyc[LEN]; }s[M]; int n,m,len,len2; int nex[LEN]; void kmp(int id){ memset(nex,0,sizeof(nex)); for(int i=2;i<=len;i++){ int NEX=nex[i-1]; while(NEX && s[id].s[NEX+1]!=s[id].s[i]){ NEX=nex[NEX]; } if(s[id].s[NEX+1]==s[id].s[i])nex[i]=NEX+1; } int NEX=nex[len]; while(NEX){ if(len2-NEX<=n)s[id].cyc[++s[id].numc]=len-NEX; NEX=nex[NEX]; } s[id].cyc[++s[id].numc]=len; } bool cmp(STR s1,STR s2){ int len=min(s1.numc,s2.numc); for(int i=1;i<=len;i++){ if(s1.cyc[i]>s2.cyc[i])return 1; if(s1.cyc[i]<s2.cyc[i])return 0; } if(s1.numc>s2.numc)return 1; if(s1.numc<s2.numc)return 0; return s1.id<s2.id; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ scanf("%s",s[i].s+1),len=strlen(s[i].s+1),len2=len<<1; s[i].id=i; kmp(i); } sort(s+1,s+1+m,cmp); for(int i=1;i<=m;i++){ printf("%s\n",s[i].s+1); } }
- 1
信息
- ID
- 6087
- 时间
- 2000ms
- 内存
- 512MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者