首页 > 其他分享 >复健week1

复健week1

时间:2024-09-08 23:02:24浏览次数:11  
标签:复健 loc int KMP fail ans week1 border

复健week1

主要是字符串基础,都是以前做过的题。

KMP

LG3375 【模板】KMP

唯一没忘的东西,原理理解后比较简单,懒得详细写了。

复杂度证明:\(j\) 指针至多加 \(n\) 次,无法匹配后也至多回退 \(n\) 次。复杂度 \(O(n)\)

for(int i=2,j=0;i<=n;++i){
    while(j&&s[i]!=s[j+1])j=nxt[j];
    if(s[i]==s[j+1])++j;
    nxt[i]=j;
}
for(int i=1,j=0;i<=m;++i){
    while(j&&t[i]!=s[j+1])j=nxt[j];
    if(t[i]==s[j+1])++j;
    if(j==n)printf("%d\n",i-n+1);
}

CF1200E Compress Words

sol1

设需要合并第 \(i\) 个字符串,长度为 \(len\),只需求目前已经得到答案串的后 \(len\) 位的后缀与 \(s_i\) 的前缀的最长匹配长度。这个可以做到 \(O(len)\)。既可以先求 \(s_i\) 的 \(border\) 然后让 \(s_i\) 与 \(ans\) 匹配(24.09.05),也可以直接让 \(s_i\) 和 \(ans\) 后 \(len\) 位拼在一起直接求 \(border\)(21.10.25)。总复杂度 \(O(n)\)

scanf("%d",&n);
scanf("%s",ans+1);top=strlen(ans+1);
for(int T=2;T<=n;++T){
    scanf("%s",s+1);m=strlen(s+1);
    int len=min(top,m);
    for(int i=1;i<=len;++i)t[i]=ans[top-len+i];
    kmpself();int j=0;
    for(int i=1;i<=len;++i){
        while(j&&s[j+1]!=t[i])j=nxt[j];
        if(s[j+1]==t[i])++j;
    }
    for(int i=j+1;i<=m;++i)ans[++top]=s[i];
}
printf("%s",ans+1);

sol2

还可以二分加字符串哈希,比较好理解。不过这个哈希貌似要能做到快速求一段字符串的哈希值,因此可能需要多重哈希。我的想法是用一个前缀异或,一个前缀和,一个前缀积,可能可以保证正确率。懒得写了。

LG4824 [USACO15FEB] Censoring S

模式串 \(s\),文本串 \(t\)。匹配时记录一下对于 \(t\) 的每一位 \(s\) 匹配了多少位,然后若 \(s\) 匹配完成则从不在这次匹配的 \(t\) 位置开始,可以用一个维护答案(感觉栈应该是最好写的)。

LG3435 [POI2006] OKR-Periods of Words

容易发现对于每一个前缀,只需要求出其最短 \(border\) 即可。然后不可能每次都跳 \(fail\),可以类似并查集优化一下。

for(int i=2,j=2;i<=n;++i,j=i){
    if(nxt[j]){
        j=nxt[j];
        if(nxt[j])j=nxt[j];//j<i,由于之前已经优化过了,这次可以直接定义到最小border
    }
    if(nxt[i])nxt[i]=j;//直接将nxt[i]定义为最短的border,这样以后可以更快找
    ans+=i-j;
}

LG2375 [NOI2014] 动物园

一个直观的想法就是直接跳 \(fail\) 直到 \(border\) 长度小于等于目前子串的 \(\frac{1}{2}\), 然后答案统计就是这个 \(border\) 的 \(border\) 有多少个,这个可以转移。然而如果这样朴素地执行对于 aaaa...a 这种串显然会 TLE。其实只需要跑一次 KMP 求出答案,然后再跑一次限制 \(border\) 长度的 KMP 即可找到需要定位的 \(border\),然后直接用第一次 KMP 的答案即可。

manacher

非常好奇当年是怎么花费了一个下午还没学懂的。只要记住几个和 Z 函数非常相似的定义就好理解了。\(maxr\):已找到的回文串的最远右侧位置。\(loc\):取到 \(maxr\) 的回文串的中心。然后 \(ext_i\) 表示以 \(i\) 为中心的回文串的向一边延申的长度。

容易发现这个 \(ext_i\) 的定义只能解决长度为奇数的字符串,中间插板即可。注意不要插成 a|a|a|a|a,而要插成 |a|a|a|a|,不然单独的一个 a 没法判断。

复杂度证明:\(mxr\) 指针最多向右移动 \(n\) 次,\(ext\) 被 \(mxr\) 限制。

模板:LG3805。双倍经验:LG1723

inline void manacher(){
	for(int i=1,loc=0,mxr=0;i<=n;++i){
		ext[i]=min(mxr-i+1,ext[2*loc-i]);
		while(s[i+ext[i]]==s[i-ext[i]])++ext[i];
		if(i+ext[i]-1>=mxr)mxr=i+ext[i]-1,loc=i;
	}
}

int main(){
	int T;scanf("%d",&T);
	while(T--){
		scanf("%s",t+1);m=strlen(t+1);
		s[0]='%';
		for(int i=1;i<=m;++i)s[2*i-1]='|',s[2*i]=t[i];
		n=2*m+1;s[n]='|',s[n+1]='*';
		manacher();
		int maxv=0;
		for(int i=1;i<=n;++i)maxv=max(maxv,ext[i]-1);
		printf("%d\n",maxv);
	}	
	return 0;
}

LG5446 [THUPC2018] 绿绿和串串

不难,在细节上栽跟头比较多,直接看代码算了

//插板省略
manacher();
for(int i=1;i<=m;++i)len[i]=(ext[2*i]-1)/2;
for(int i=m;i>=1;--i){
    if(i+len[i]>=m)ans[i]=1;
    else if(i+len[i]<=m&&ans[i+len[i]]&&i==len[i]+1)ans[i]=1;
}
for(int i=1;i<=m;++i)//i要从1开始,自作聪明了属于是
    if(ans[i])printf("%d ",i);
puts("");
memset(ans,0,sizeof(int)*(m+1));

LG6216 回文匹配

算是 KMP + manacher 的融合题吧。难度主要在计数,其实也不是很难。

考虑对于中心为 \(loc\) 的回文串,左右边界为 \(l,r\),我们考虑对匹配的左端在 \(l\sim loc-(x+1)/2\) 的字符串计数,答案即为(其中 \(a_i\) 表示左端能否匹配)

\[\sum_{i=l}^{loc-(x+1)/2}a_i\times(i-l+1) \]

优化一下可以得到

\[\sum_{i=l}^{loc-(x+1)/2} (a_i\times i)+ (1-l)\times \sum_{i=l}^{loc-(x+1)/2}a_i \]

维护一下 \(a_i\) 和 \(a_i\times i\) 前缀和即可。

对于右侧同理,特判一下模式串长度为奇数的情况,此时还需考虑模式串的中心和回文串中心对齐时恰好能匹配。

答案对 \(2^{32}\) 取模,调了1h没发现,无语了

const int N=3000006;
int n,m,nxt[N],ext[N];
ll lef[N],rit[N],sml[N],smr[N],dsl[N],dsr[N],ans;
char s[N],t[N];

int main(){
	scanf("%d%d",&n,&m);
	scanf("%s%s",s+1,t+1);
	for(int i=2,j=0;i<=m;++i){
		while(j&&t[j+1]!=t[i])j=nxt[j];
		if(t[j+1]==t[i])++j;
		nxt[i]=j;
	}
	for(int i=1,j=0;i<=n;++i){
		while(j&&t[j+1]!=s[i])j=nxt[j];
		if(t[j+1]==s[i])++j;
		if(j==m)lef[i-m+1]++,rit[i]++;
	}

	for(int i=1,loc=0,mxr=0;i<=n;++i){
		ext[i]=min(ext[2*loc-i],mxr-i+1);
		while(s[i-ext[i]]==s[i+ext[i]]&&i-ext[i]>=1&&i+ext[i]<=n)++ext[i];
		if(i+ext[i]-1>=mxr)mxr=i+ext[i]-1,loc=i;
	}
	for(int i=1;i<=n;++i){
		dsl[i]=dsl[i-1]+lef[i]*i;
		dsr[i]=dsr[i-1]+rit[i]*i;
		sml[i]=sml[i-1]+lef[i];
		smr[i]=smr[i-1]+rit[i];
		//printf("%d %d %d %d\n",sml[i],smr[i],dsl[i],dsr[i]);
	}
	for(int i=1;i<=n;++i){
		ll x=(m+1)/2,l=i-ext[i]+1,r=i+ext[i]-1;
		//printf("%d %lld %lld %lld\n",ext[i],dsl[i-x]-dsl[l-1]+(sml[i-x]-sml[l-1])*(1-l),(smr[r]-smr[i+x-1])*(r+1)-dsr[r]+dsr[i+x-1],(rit[i+m/2]==1&&i+m/2<=r)*(r-i-x+2));
		if(l<=i-x)ans+=dsl[i-x]-dsl[l-1]+(sml[i-x]-sml[l-1])*(1-l);
		if(r>=i+x)ans+=(smr[r]-smr[i+x-1])*(r+1)-dsr[r]+dsr[i+x-1];
		if(m%2==1&&rit[i+m/2]==1&&i+m/2<=r)ans+=(r-i-x+2);
	}
	printf("%lld\n",ans%(1ll<<32));//byd答案取模
	return 0;
}

ACAM

就是 KMP on Trie。直接看代码算了。

int ch[N][26],tot=1,nxt[N],bot[N];//nxt即fail
queue<int>q;
inline void insert(char *s){
    int n=strlen(s+1),u=1;
    for(int i=1;i<=n;++i){
        int w=s[i]-'a';
        if(!ch[u][w])ch[u][w]=++tot;
        u=ch[u][w];
    }++bot[u];
}
inline void build(){
    for(int i=0;i<26;++i)ch[0][i]=1;
    nxt[1]=0;q.push(1);
    while(!q.empty()){
        int u=q.front(),f=nxt[u];q.pop();
        for(int i=0;i<26;++i){
            int v=ch[u][i];
            if(!v){ch[u][i]=ch[f][i];continue;}
            nxt[v]=ch[f][i];
            q.push(v);
        }
    }
}

LG3808 AC 自动机(简单版)

inline int eazy_version_wa(char *s){//统计多少个不同的模式串在文本串中出现过(错误做法)
    int n=strlen(s+1),u=1,ans=0;
    for(int i=1;i<=n;++i){
        int w=s[i]-'a';
        if(ch[u][w])u=ch[u][w];
        else u=ch[nxt[u]][w];
        ans+=bot[u];
    }
    return ans;
}
/*
hack:
2
abaaa
b
abaaa
无法定位到字符串 b,其中 b 对应的节点为 ab 中 b 的 fail
*/
inline int eazy_version(char *s){//统计多少个不同的模式串在文本串中出现过(正确做法)
    int n=strlen(s+1),u=1,ans=0;
    for(int i=1;i<=n;++i){
        int w=s[i]-'a',v=ch[u][w],vv=v;
        while(vv>1&&bot[vv]!=-1){//模式串都是从根开始,因此要定位到这个模式串,要利用fail边
            ans+=bot[vv];
            bot[vv]=-1;
            vv=nxt[vv];
        }
        u=v;
    }
    return ans;
}

LG5357 【模板】AC 自动机

简单版 v2,与这题做法可以相同。

\(u\to fail_u\) 构成了一张有向无环图。因为 \(fail_u\) 为 \(u\) 的一个子串,然后 \(fail_u\) 又能恰好对应一个从根开始的字符串(可能是一个模式串),跑拓扑排序即可求得所有模式串的出现次数。

inline void run_time(char *t){
    memcpy(tmp,ind,sizeof(int)*(tot+1));
    int n=strlen(t+1);
    for(int i=1;i<=tot;++i)
        if(!tmp[i])q.push(i);
    for(int i=1,u=1;i<=n;++i){
        int w=t[i]-'a',v=ch[u][w];
        if(v>1)++val[v];
        u=v;
    }
    while(!q.empty()){
        int u=q.front();q.pop();
        if(lis[u].size()){
            for(auto x:lis[u])
                tim[x]=val[u];
        }
        int v=fail[u];
        val[v]+=val[u];
        --tmp[v];
        if(!tmp[v])q.push(v);
    }
}

LG3121 [USACO15FEB] Censoring G

LG4824翻版,又自己打了一遍练手,没啥难度(但是中间发现函数里面开数组爆了)。

LG3966 [TJOI2013] 单词

额暴力 KMP 匹配可以过,复杂度 \(O(n\sum|T|)\),没办法,\(n\) 太小了。

可以把这些字符串拼在一起作为文本串,中间插上特殊符号作为分割,然后跑 ACAM。自己又写了一遍,结果又忘记在主函数中建立 ACAM。

杂记

还打了一场 CF div3 和 ABC,表现平平吧,现在还没补题。

周末本来准备多做点题的,至少计划要把写过的 ACAM 的题都搞完。但周六睡过去了,周天又去医院检查了一个上午(这个身体真是越来越差了,不知道这个拉肚子能不能治好,经典身体难受检查屁事没有)。

然后去参加了一下校基地的宣讲,场馆人坐满了后面还站了一排,很闷很热,听到后面脑袋要炸了。那个老师讲了大概 \(50\rm min\),感觉对这个新生的吸引力蛮大的(但是挺多了估计就挺烦了,就和 sjf 一样),不过宣传片很燃(但是说实话有点意义不明的运镜)。

感觉一周没干什么事,晚上还有活动,下周就开始上课了。

欸晚上活动搞完了,现在已经晚上11点了,先是学生会的一群 GPA 贼高的学长过来分享经验,感觉狠狠地被压力到了;然后又是破冰行动,一堆人坐在草坪上搞活动,对我来说挺坐牢的。byd下午写了个图论还没调出来,真是失败的一天啊。还是看看下周吧

标签:复健,loc,int,KMP,fail,ans,week1,border
From: https://www.cnblogs.com/BigSmall-En/p/18403679

相关文章

  • mini-lsm通关笔记Week1Day7
    Summary在上一章中,您已经构建了一个具有get/scan/put支持的存储引擎。在本周末,我们将实现SST存储格式的一些简单但重要的优化。欢迎来到Mini-LSM的第1周零食时间!在本章中,您将:在SST上实现布隆过滤器,并集成到LSM读路径get中。以SST块格式实现对key存储的压缩。要将测试用例......
  • mini-lsm通关笔记Week1Day6
    项目地址:https://github.com/skyzh/mini-lsm个人实现地址:https://gitee.com/cnyuyang/mini-lsmSummary在本章中,您将:使用L0flush实现LSM写路径。实现逻辑以正确更新LSM状态。要将测试用例复制到启动器代码中并运行它们,cargoxcopy-test--week1--day6cargoxsch......
  • DP训练7 重新复健
    很久没写题了大概有半个月吧中间有许多忙事然后这几天开学也是手机坏掉了电脑坏掉了然后又要招新最重要的是复健ccpc今年去不了了因为报名没注意过时间了第一道错排题目做了这道题我才知道错排的首先错排是什么就是说abcd....这么多个人没有一个人可以站在......
  • mini-lsm通关笔记Week1Day5
    项目地址:https://github.com/skyzh/mini-lsm个人实现地址:https://gitee.com/cnyuyang/mini-lsmTask1-TwoMergeIterator在此任务中,您需要修改:src/iterators/two_merge_iterator.rs你已经在Week1Day2中实现了一个合并迭代器,它合并相同类型的迭代器(如:memtable迭代器)。既然......
  • BaseCTF2024-week1-Crypto部分题目wp
    先放一下官方的wp(我这里只放我出的题):Docs(feishu.cn)babypackfromCrypto.Util.numberimport*importrandomflag=b'BaseCTF{}'m=bytes_to_long(flag)bin_m=bin(m)[2:]length=len(bin_m)a=[1]sum=1foriinrange(length-1):temp=random.randint(2*sum+1,4*su......
  • NSSCTF [HNCTF 2022 Week1]Interesting_include
    <?php//WEB手要懂得搜索//flagin./flag.phpif(isset($_GET['filter'])){$file=$_GET['filter'];if(!preg_match("/flag/i",$file)){die("error");}include($file);}else{highlight_file(__......
  • mini-lsm通关笔记Week1Day4
    项目地址:https://github.com/skyzh/mini-lsm个人实现地址:https://gitee.com/cnyuyang/mini-lsmTask1-SSTBuilder在此任务中,您需要修改:src/table/builder.rssrc/table.rsSST由存储在磁盘上的数据块和索引块组成。通常,数据块都是懒加载的-直到用户发出请求,它们才会被加载......
  • MO 复健
    不定期传一些最近写的MO题.如图,在锐角\(\triangleABC\)中,\(O,H\)分别是外心和垂心,\(K\)是\(AH\)的中点,\(P\)在\(AC\)上,且满足\(\angleBKP=90^\circ\).求证:\(OP\parallelBC\).证明:如图,作直线\(BH\)交\(AC\)于点\(D\),连结\(KD\);分别过\(O,P\)作\(B......
  • 【复健】LCA复健笔记
    LCA复健笔记展开目录目录LCA复健笔记什么是LCA怎么求LCA暴力求LCA倍增优化应用场景不适合的应用场景什么是LCA最近公共祖先/最深公共祖先,顾名思义,两个点的公共祖先中离它们最近/深度最大的那个。怎么求LCA这里使用倍增优化算法,因为之前看不懂所以我觉得应该补一下......
  • 24暑集训Week1
    24暑集训Week1夜行的人,若你不唱歌的话,不惊醒这黑夜的话,就永远也走不出呼蓝别斯了。这重重的森林,这崎岖纤细的山路,这孤独疲惫的心。亲爱的,哪怕后来去到了城市,$$走$$夜路时也要大声地唱歌,像喝醉酒的人一样无所顾忌。大声地唱啊,让远方的大棕熊也听到了,也静静起身,为你在遥远的地......