题解: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;
}

浙公网安备 33010602011771号