1 条题解

  • 0
    @ 2025-8-24 22:25:19

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar 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;
        }
    }
    

    当总序列长度足够大1212 是一个超级大的数),连续序列长度为 66 时,输出节选如下:

    • 第一列为连续序列举例,
    • 第二列为出现次数(扣除重复)
    • 第三列为循环节(未间隔)
    100000 5102:6
    010000 5096:56
    100010 5075:46
    001000 5069:456
    100100 4995:36
    010010 4989:356
    101010 4698:246
    000000 3645:123456
    

    可以看到,我们的猜想有点道理。

    如果把各个例子的循环节用 00 补全到六位数,依次便为600000/560000/460000/456000/360000/356000/246000 123456,数字越小,预测可能性越小。

    那么如果总序列长度不够大呢?比如为 1010? 结果如下:

    100000 405:6
    010000 405:56
    100010 405:46
    001000 404:456
    100100 399:36
    010010 399:356
    101010 378:246
    000000 297:123456
    

    出现次数重复了,似乎不是非常适用。

    但是考虑到总序列长度为 1010 的情况下,两个长度为 66 的连续序列最多只有 44 位重叠,所以长度大于等于 55 的循环节是无用的,因而将其去掉。(留下各个数字都含有的 66 不影响结果,而且便于排序

    100000 405:6
    100010 405:46
    100100 399:36
    101010 378:246
    000000 297:12346
    

    好啊,很好啊。

    至此我们打通了思路(结论显然正确,严格证明移步其他题解绝对不是因为本蒟蒻不会证),接下来开始代码实现。

    具体算法:

    对于循环节的处理,我们利用 KMP 的思路,对每一个连续序列求出失配数组,然后从连续序列的末尾开始不断跳失配。

    过程中所得的下标用其长度 lenlen 减去的结果恰好就是我们所需的各个循环节。(注意长度大于 nlenn-len 的循环节不应记录)

    一张简易的的原理图:(1463114\Rightarrow6\Rightarrow3\Rightarrow1 为过程中下标变化) 得到每个连续序列的循环节后,按照

    1. 首位更大
    2. 数量更多
    3. 输入顺序靠前

    对其排序即可得到答案。

    代码实现:

    #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
    上传者