题解:P16120 [USTCPC 2026] Hamming Dominance
lailai0916 · · 题解
题意简述
长度为
相同的循环移位结果仅算一个字符串,不能按移位次数重复计数。
解题思路
先允许重复,将每个循环起点都看成一个对象,最后再处理相同字符串的重复计数。
把当前串无限循环,记从位置
设
这个恒等式不要求
当
于是,对于每个偏移
翻转位置
因此,为每个
本题的内存限制需要单独处理。常规线段树为每个节点保存最大值、次数和懒标记,并按
对于区间
内部节点保存 val 和 cnt,分别表示最大值和出现次数;叶子仅保存数值 a,因为一个叶子的出现次数总为 short。出现次数至多为
不再为内部节点显式保存懒标记,而是保留下面的关系。设节点最大值为
若修改完整覆盖当前节点,仅改变它保存的最大值,不修改子节点,次数也不变。若需要递归修改子节点,先由旧数值计算
这样,每棵树仅保存三个长度为 query 统一读取叶子和内部节点,update 在局部变量 tag 中暂存通过上式恢复的偏移。
最后消除循环移位产生的重复。设当前串的最小循环周期为
每次翻转后,用前缀函数重新求最小周期。设最长相等真前后缀长度为 nxt[n],令
每次翻转进行
参考代码
#include <bits/stdc++.h>
using namespace std;
using pii=pair<int,int>;
const int N=2005;
struct SEG
{
short val[N],cnt[N],a[N];
void build(int l,int r)
{
if(l==r){a[l]=0;return;}
int mid=l+r>>1;
val[mid]=0;
cnt[mid]=r-l+1;
build(l,mid);
build(mid+1,r);
}
pii query(int l,int r)
{
return l==r?pii(a[l],1):pii(val[l+r>>1],cnt[l+r>>1]);
}
void update(int l,int r,int x,int y,int v)
{
if(l==r){a[l]+=v;return;}
int mid=l+r>>1;
if(x<=l&&r<=y){val[mid]+=v;return;}
auto ls=query(l,mid),rs=query(mid+1,r);
int tag=val[mid]-max(ls.first,rs.first);
if(x<=mid)update(l,mid,x,y,v);
if(y>mid)update(mid+1,r,x,y,v);
ls=query(l,mid);
rs=query(mid+1,r);
val[mid]=max(ls.first,rs.first)+tag;
cnt[mid]=(ls.first>=rs.first?ls.second:0)+(rs.first>=ls.first?rs.second:0);
}
}tr[N];
int nxt[N];
bool s[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--)
{
int n;
cin>>n;
fill(s,s+n+1,0);
for(int i=1;i<n;i++)tr[i].build(1,n);
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
s[x]=!s[x];
int v=s[x]?1:-1,ans=n;
for(int j=1;j<n;j++)
{
int l=x-j+1;
if(l>0)tr[j].update(1,n,l,x,v);
else
{
tr[j].update(1,n,1,x,v);
tr[j].update(1,n,l+n,n,v);
}
ans+=tr[j].cnt[n+1>>1];
}
for(int j=2;j<=n;j++)
{
int k=nxt[j-1];
while(k&&s[j]!=s[k+1])k=nxt[k];
if(s[j]==s[k+1])k++;
nxt[j]=k;
}
int len=n-nxt[n];
if(n%len)len=n;
int cnt=n/len;
if(i>1)cout<<' ';
cout<<ans/(cnt*cnt);
}
cout<<'\n';
}
return 0;
}