C
题意:第i天在原来字符串的基础上在字符串的前面或者后面添加一个字符。问添加完字符后有多少种不同长度的循环节。
定义循环节为能把原字符串分割成长度为k的一个或多个部分。并且后面可以有或者没有循环节的开头部分。
也就是说 s[1]~s[n-x] == s[x+1]~ s[n] 则说明循环节为x 。
做法一 hash+二分:
1.明显,对于长度为len的字符串,最少有一个长度为len的循环节。
2.答案单调性,如果一个长度为r的字符串有长度为x的循环节。那么长度x~r的所有字符串都有长度为x的循环节(手动模拟下可以证明,将原字符串不断删去开头或者末尾的字符必然还是满足s[1]~s[n-x] == s[x+1]~ s[n])
同理如果长度为r的字符串没有长度为x的循环节,那么长度大于r的字符串也一定没有长度为x的循环节(同样手动模拟在字符串后面或者前面添加字符)
那么我们可以想到,对长度为i=(1~n)的循环节我们在原字符串的第i~第n阶段 进行二分。找到拥有循环节i 的最大字符串长度r
那么i~r 都有循环节i, 结果加1 那么我们可以通过前缀和的方式将区间修改 变成单点修改。让a[i]++, a[r+1]-- 则前缀和a[1]+...a[i]为
第i个阶段的字符串的循环节的种类数。
为了对字符串二分。我们先把最终阶段的字符串hash,然后记录一下每次变化后字符串的左端点位置和右端点位置。
(具体做法是把head 设置为maxlen+1 ,tail 设置为maxlen+2 然后head往前减 tail往后加 最后把L[i]-=head, R[i]-=head则可以得到每个阶段的左右端点)
然后每次二分check看L[i]~R[i]-x 是否等于x+1~R[i] 来判断第i 阶段可不可行。
总复杂度
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int maxn=2e6+6;
int n;
char s[10];
char ss[10];
int L[maxn],R[maxn],a[maxn];
struct Hash
{
ull ha[maxn],p[maxn],base=233;
void get_hash(char *buf)
{
int n=strlen(buf);
p[0]=1;
for(int i=1;i<=n;i++)p[i]=p[i-1]*base;
for(int i=1;i<=n;i++)
{
ha[i]=(ha[i-1]*base+(buf[i-1]+1));
}
}
ull query(int l,int r)
{
ull ans=ha[r]-ha[l-1]*p[r-l+1];
return ans;
}
}hh;
bool check(int q,int x)
{
return hh.query(L[q],R[q]-x)==hh.query(x+L[q],R[q]);
}
int main()
{
scanf("%d",&n);
int head=1e6+1,tail=1e6+2;
char t[maxn];
for(int i=1;i<=n;i++)
{
scanf("%s%s",s,ss);
char ch=ss[0];
if(ch=='s'&&ss[1]=='i')ch='z';
if(s[0]=='p')t[head--]=ch;
else t[tail++]=ch;
L[i]=head+1;
R[i]=tail-1;
}
hh.get_hash(t+head+1);
for(int i=1;i<=n;i++)L[i]-=head,R[i]-=head;
for(int i=1;i<=n;i++)
{
int l=i,r=n;
int mid;
while(l<=r)
{
mid=(l+r)>>1;
if(check(mid,i))l=mid+1;
else r=mid-1;
}
a[r+1]--;
a[i]++;
}
for(int i=1;i<=n;i++)
{
a[i]+=a[i-1];
printf("%d\n",a[i]);
}
return 0;
}
F
题意:给你n个点,坐标范围为1e5 ,选不超过3条的横线和不超过3条的竖线。相邻线的距离固定为r,求位于这些线上的点的数量的最大值。
做法:用一棵线段树维护值域上 第i, i+r, i+r*2 行上点的总数。 也就是说对于b[i] = a[i]+a[i+r]+a[i+r*2] 然后询问整个区间的最大值。
然后对于列,我们用一个值域上的vector 存储每列上对应的x的值。然后我们 每次取出 第 i,i+r,i+r*2 列,然后将这些列上的x 在线段树的对应点处删除。 则 ans更新为 三列的元素个数加上 线段树的区间最大值。 每次更新完答案我们再把删除的x加回去。
(对于一个x 他是 x,x-r , x-r*2 节点中的值, 所以这三个节点 需要变化)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=100001;
int a[100005];
int b[100005];
int n,r;
vector<int>v[100005];
struct segment_tree
{
int l,r;
int mx;
}t[maxn*4];
void pushup(int o)
{
t[o].mx=max(t[o<<1].mx,t[o<<1|1].mx);
}
void build(int o,int l, int r)
{
t[o].l=l; t[o].r=r;
if(l==r)
{
t[o].mx=b[l];
return ;
}
int mid=(l+r)>>1;
build(o<<1,l,mid);
build(o<<1|1,mid+1,r);
pushup(o);
}
void change(int o,int x,int d) // 单点更新
{
if(t[o].l==t[o].r)
{
t[o].mx+=d;
return ;
}
int mid=(t[o].l+t[o].r)>>1;
if(x<=mid)change(o<<1, x, d);
else change(o<<1|1, x, d);
pushup(o);
}
int query(int o, int l, int r)
{
if(l<=t[o].l&&t[o].r<=r)
{
return t[o].mx;
}
int mid=(t[o].l+t[o].r)>>1;
int mx=-1;
if(l<=mid)mx=max(query(o<<1,l,r),mx);
if(r>mid)mx=max(query(o<<1|1,l,r),mx);
return mx;
}
int main()
{
scanf("%d%d",&n,&r);
int x,y;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&x,&y);
a[x+1]++;
v[y+1].push_back(x+1);
}
for(int i=1;i<=maxn;i++)
{
b[i]+=a[i];
if(i+r<=maxn)b[i]+=a[i+r];
if(i+r+r<=maxn)b[i]+=a[i+r+r];
}
build(1,1,maxn);
int ans=-1;
for(int i=1;i<=maxn;i++)
{
vector<int>tmp;
for(auto j:v[i])
{
tmp.push_back(j);
}
if(i+r<=maxn)
{
for(auto j:v[i+r])
{
tmp.push_back(j);
}
}
if(i+r+r<=maxn)
{
for(auto j:v[i+r+r])
{
tmp.push_back(j);
}
}
for(auto k:tmp)
{
change(1,k,-1);
if(k-r>=1)change(1,k-r,-1);
if(k-r*2>=1)change(1,k-r*2,-1);
}
ans=max(ans, (int)(tmp.size())+query(1,1,maxn) );
for(auto k:tmp)
{
change(1,k,1);
if(k-r>=1)change(1,k-r,1);
if(k-r*2>=1)change(1,k-r*2,1);
}
}
printf("%d\n",ans);
return 0;
}