[JSOI2019]节日庆典 做题心得
[JSOI2019]节日庆典 做题心得
一个性质有趣的字符串题
这要是在考场上我肯定做不出来吧
一开始还以为要 SAM 什么的暴力搞,没想到只用到了 函数 —— 也是我生疏了罢
(学了啥忘了啥,这可怎么去wc啊啊啊
思路
考虑维护候选集合 ,表示这个位置 可能 是最优位置。
假设我们可以知道 ,拿 函数求一下 就可以知道是不是最优的了。
大胆猜测一个性质,要么可以快速的找 中最小的位置,要么 的大小不会很大,可以暴力找。
第一个思路不太好搞,那看来就选第二个思路吧
假设现在考虑前 位
首先可以来一个优化:如果还没循环,就比出来了胜负,那么大的那个一定不会是最优解 —— 并且随着 的增加,显然不会翻身成更优的
那现在对于 ,一定有 。
考虑两个 ,如果 和 不重合,看来没什么性质
如果重合,似乎有些有趣的性质,先放结论:
如果重合,即 ,则 一定不会是最优的
我相信这个图还挺清楚的
红色是 的 。然后设 到 前面是 ,然后红色减去 之后剩下的是 ,是从 开始的。由于两个红色相等,所以 是红色的前缀,对应到后面,它也是红色的后缀(即它是红色的 border)
是 往后数一个 之后的开始位置
设 前面的是 。 开始的循环串就是 , 同理
然后发现 作为一个整体出现,把它看成 。然后就清楚多了:
如果 ,那么 开始是最小的;
如果 ,那么 开始是最小的;
如果 ,那么三个一样大;
总之,忽略 ,对最大值没有影响
那么 集合中,每往前数一个数,它到上一个的距离,都比上一个到 的距离大。和启发式合并类似的,有一个基本事实:
一个数加上比它大的数,至少翻一倍
那 集合中数到 的距离,每次至少翻一倍,所以 的大小是 的。
每次新加入元素的时候维护一下即可,然后分段考虑一下,求最小的那个开始位置。
#include <bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
#define N 10000007
#define F(i,l,r) for(int i=l;i<=r;++i)
#define D(i,r,l) for(int i=r;i>=l;--i)
#define Fs(i,l,r,c) for(int i=l;i<=r;c)
#define Ds(i,r,l,c) for(int i=r;i>=l;c)
#define MEM(x,a) memset(x,a,sizeof(x))
#define FK(x) MEM(x,0)
#define Tra(i,u) for(int i=G.st(u),v=G.to(i);~i;i=G.nx(i),v=G.to(i))
#define p_b push_back
#define sz(a) ((int)a.size())
#define all(a) a.begin(),a.end()
#define iter(a,p) (a.begin()+p)
int n;
char s[N];
void Input()
{
scanf("%s",s+1);
n=strlen(s+1);
}
int z[N];
void Z_Function()
{
z[1]=n; F(i,2,n) z[i]=0;
int l=0,r=0;
F(i,2,n)
{
if (i<=r) z[i]=min(z[i-l+1],r-i+1);
while(i+z[i]<=n and s[i+z[i]]==s[z[i]+1]) ++z[i];
if (i+z[i]-1>=r) l=i,r=i+z[i]-1;
}
}
int c[N],cp=0;
int stk[N],top=0;
void fuckoff(int k)
{
while(top) stk[top--]=0;
F(i,1,cp)
{
int x=c[i];
while(top and s[stk[top]+k-x]>s[k])
{
--top;
}
if (top and s[stk[top]+k-x]<s[k]) continue;
stk[++top]=x;
}
F(i,1,cp) c[i]=0; cp=top; F(i,1,top) c[i]=stk[i];
while(top) stk[top--]=0;
D(i,cp,1)
{
while(top and k-stk[top]+1>stk[top]-c[i]) --top;
stk[++top]=c[i];
}
F(i,1,cp) c[i]=0; cp=top; F(i,1,top) c[i]=stk[top-i+1];
}
int getmin(int k)
{
int ansp=c[1];
F(i,2,cp)
{
int x=c[i];
// printf("x=%d cur=%d\n",x,ansp);
int LCP1=k-x+1;
int LCP2=min(z[ansp+LCP1],k-(ansp+LCP1)+1);
// printf(" LCP2=%d\n",LCP2);
if (LCP2<k-(ansp+LCP1)+1)
{
int p1=ansp+LCP1+LCP2;
int p2=LCP2+1;
// printf(" case2 p1=%d p2=%d\n",p1,p2);
if (p2>k) continue;
if (s[p1]>s[p2])
{
// puts(" better");
ansp=x;
}
}
else
{
int LCP3=min(z[LCP2+1],k-LCP2);
int p1=LCP3+1;
int p2=LCP2+LCP3+1;
// printf(" LCP3=%d\n",LCP3);
// printf(" case3 p1=%d p2=%d\n",p1,p2);
if (p2>k) continue;
if (s[p1]>s[p2])
{
// puts(" better");
ansp=x;
}
}
}
return ansp;
}
void Sakuya()
{
Z_Function();
F(i,1,n)
{
c[++cp]=i;
fuckoff(i);
printf("%d ",getmin(i));
}
puts("");
}
void IsMyWife()
{
Input();
Sakuya();
}
}
#undef int //long long
int main()
{
Flandre_Scarlet::IsMyWife();
getchar();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】