题解:AT_abc312_h [ABC312Ex] snukesnuke

前言

本文同步自洛谷专栏,题目传送门

前置知识:kmp,调和级数。

有关结论:一个字符串的最小正周期 \(T\) 为其长度 \(len\) 减去其最长 border 长度 \(fail_{len}(nxt_{len})\)

分析

首先可以发现,如果有两个串分别形如 \(AA,AAA\)\(A\) 代表一个字符串),那么会互相影响,这启示我们通过循环节考虑。

假设当前的串长度为 \(len\),利用 kmp 算法求得其最小正周期为 \(T\),若有 \(T|len\),则该串循环节长度即为 \(T\),否则为 \(len\)

下面改令 \(T\) 表示其循环节长度。

再有一个观察:如果两个串的循环节不同,那么两个串之间一定互不影响。

那么可以利用 map、哈希等方式给每种循环节分配一个编号,编号相同的之间才会有影响。记当前的编号为 \(id\)

于是问题可以转化为如下形式:

给定若干个正整数集合,每次指定一个集合 \(S_{id}\),并给出一个正整数 \(K=\frac{len}{T}\le len\),求解最小的、不在该集合中的、是 \(K\) 的倍数的数,并插入集合。

由于各个集合之间相互独立,不妨只考虑一个。

如果我们每次都从小到大枚举倍数,那么每次给出 \(K=1\),查询次数就会卡至 \(O(n^2)\)

如果用 set 维护连续段,那 \(K=2\) 也可以直接卡掉。

发现被卡的原因是浪费了很多时间在枚举已知不行的数上,考虑避免这一部分浪费。

考虑对于同一个 \(K\),若当下 \(xK\) 已经出现在集合中,那么以后枚举这个 \(x\) 将无效。

也就是说,对于相同的 \(K\),答案具有单调性。

那么可以采取最暴力的方式:暴力记录每一个 \(K\) 当前的答案。(由于有若干个不同的集合,所以内层要开哈希表,未出现的不维护)

同时还有一个结论:一个集合中的 \(maxn\le \sum K\)(此处 \(\sum K\) 为与该集合有关的 \(K\))。

考虑归纳证明。每次查询一个 \(K\) 导致 \(maxn\) 增长时,新的 \(\max\le maxn+K\)

那么,对于一个集合中的总查询次数,应为 \(O(\sum_{i=1}^{n}\frac{maxn}{i})\),即为 \(O((\sum K)\ln{n})\)

则全局查询次数为 \(O((\sum K)\ln{n})\)

\(A=\sum len\ge \sum K\),则根据使用的 STL 不同,有复杂度为 \(O(A\log{n})\)\(O(A\log^2{n})\) 的做法。

实现

$\red{\text{code}}$
#include<bits/stdc++.h>
#include<bits/extc++.h>
using namespace std;
#define N 200005
#define ull unsigned long long

int n,len,fail[N],K,cnt,now;
__gnu_pbds::cc_hash_table<ull,int>mp;
__gnu_pbds::cc_hash_table<int,int>nxt[N];
set<int>st[N];
char s[N];

void kmp(){
    fail[0]=-1;
    for(int i=0,j=-1;i<len;){
        if(j==-1||s[i]==s[j]) fail[++i]=++j;
        else j=fail[j];
    }
}

void insert(){
    scanf(" %s",s),len=strlen(s),kmp();
    if(len%(len-fail[len])==0) K=len-fail[len];//找最小周期
    else K=len;
    ull hsh=0;
    for(int i=0;i<K;i++) hsh=hsh*29+s[i]-'a'+1;
    if(!mp[hsh]) mp[hsh]=++cnt;//分配编号
    now=mp[hsh],K=len/K;//自带倍数 
    int &pl=nxt[now][K];//暴力记录即可
    for(++pl;;++pl){
        if(st[now].find(K*pl)!=st[now].end()) continue;
        printf("%d ",pl),st[now].insert(K*pl);break;
    }
}

int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) insert();
    return 0;
}

posted @ 2026-06-05 10:20  Wxb2010  阅读(8)  评论(0)    收藏  举报