Week 4

P3808 【模板】AC 自动机(简单版)
P3796 【模板】AC 自动机(加强版)
P5357 【模板】AC 自动机(二次加强版)
2023ccpc女生组:H. 字符串游戏
B3637 最长上升子序列
P1115 最大子段和
P8707 [蓝桥杯 2020 省 AB1] 走方格
P1216 [USACO1.5] [IOI1994]数字三角形 Number Triangles
P1020 [NOIP1999 普及组] 导弹拦截

1|0【模板】AC 自动机(二次加强版)

1|1题目描述

给你一个文本串 Sn 个模式串 T1n,请你分别求出每个模式串 TiS 中出现的次数。

1|2输入格式

第一行包含一个正整数 n 表示模式串的个数。

接下来 n 行,第 i 行包含一个由小写英文字母构成的非空字符串 Ti

最后一行包含一个由小写英文字母构成的非空字符串 S

数据不保证任意两个模式串不相同

1|3输出格式

输出包含 n 行,其中第 i 行包含一个非负整数表示 TiS 中出现的次数。

1|4样例 #1

1|0样例输入 #1

5 a bb aa abaa abaaa abaaabaa

1|0样例输出 #1

6 0 3 2 1

1|5提示

对于 100% 的数据,1n2×105T1n 的长度总和不超过 2×105S 的长度不超过 2×106

1|6题解

一般来说简单版的AC自动机就能过,但最坏情况时间复杂度是O(模式串总长度*文本串总长度)肯定过不了
因为一条fail路径可能经过多次,所以先存一下一条路径起始点的答案数,而不走fail路径
最后存一下每个点的入度(有几个点连过来)
入度为零的即为起始点
最后遍历一遍,将后一个点答案加上当前点答案数
在加答案之前,令当前节点对应的子串答案等于当前节点答案数
然后使下一个节点入度减 1,如果它的入度为零,也将它作为起始点(说白了就是按拓扑序排一次)
这样可以保证在进入下一个节点的下一个节点前,所有连到下一个节点的节点都被遍历过

需要注意的是一个模式串可能出现多次,所以记录一下第一个与当前子串相同的子串的的编号即可

1|7Code

#include<bits/stdc++.h> #define ll long long using namespace std; const int N=2e6+10; int n,tot,ans[N],trie[N][26],fail[N],id[N],M,mp[N],ansx[N],in[N]; string s,a[N]; inline ll read() { ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } void build(string x,int y) { int u=0,L=x.size(); for(int i=0;i<L;i++) { int v=x[i]-'a'; if(!trie[u][v])trie[u][v]=++tot; u=trie[u][v]; } if(!id[u])id[u]=y; mp[y]=id[u]; } void getfail() { int u=0; queue<int> q; for(int i=0;i<26;i++) if(trie[u][i]) q.push(trie[u][i]); while(!q.empty()) { u=q.front();q.pop(); for(int i=0;i<26;i++) { if(trie[u][i]) fail[trie[u][i]]=trie[fail[u]][i],q.push(trie[u][i]); else {trie[u][i]=trie[fail[u]][i];continue;} in[fail[trie[u][i]]]++; } } } void solve(string x) { int u=0,L=x.size(); for(int i=0;i<L;i++) { int v=x[i]-'a'; u=trie[u][v]; ansx[u]++; } } void Topu() { queue<int> q; for(int i=0;i<=tot;i++) if(!in[i])q.push(i); while(!q.empty()) { int u=q.front();q.pop(); ans[id[u]]=ansx[u]; ansx[fail[u]]+=ansx[u]; in[fail[u]]--; if(!in[fail[u]])q.push(fail[u]); } } int main() { //freopen(".in","r",stdin); //freopen(".out","w",stdout); n=read(); for(int i=1;i<=n;i++) { cin>>a[i]; build(a[i],i); } getfail(); cin>>s; solve(s);Topu(); for(int i=1;i<=n;i++) cout<<ans[mp[i]]<<endl; return 0; } //**月雩·薇嫭**

2|0最长上升子序列

2|1题目描述

这是一个简单的动规板子题。

给出一个由 n(n5000) 个不超过 106 的正整数组成的序列。请输出这个序列的最长上升子序列的长度。

最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。

2|2输入格式

第一行,一个整数 n,表示序列长度。

第二行有 n 个整数,表示这个序列。

2|3输出格式

一个整数表示答案。

2|4样例 #1

1|0样例输入 #1

6 1 2 4 1 3 4

1|0样例输出 #1

4

2|5提示

分别取出 1234 即可。

2|6题解

令f[i]表示以a[i]结尾的最长的上升子序列
那么如果j<ia[j]<a[i]f[i]=max(f[i],f[j]+1)
最后答案就是max(f[i])

2|7Code

#include<bits/stdc++.h> #define ll long long using namespace std; const int N=1e6+10; ll n,a[N],f[N],ans; inline ll read() { ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int main() { //freopen(".in","r",stdin); //freopen(".out","w",stdout); n=read(); for(int i=1;i<=n;i++)cin>>a[i]; for(int i=1;i<=n;i++)f[i]=1; for(int i=1;i<=n;i++) for(int j=1;j<i;j++) if(a[i]>a[j]) f[i]=max(f[i],f[j]+1); for(int i=1;i<=n;i++)ans=max(ans,f[i]); cout<<ans; return 0; } //**月雩·薇嫭**

3|0最大子段和

3|1题目描述

给出一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大。

3|2输入格式

第一行是一个整数,表示序列的长度 n

第二行有 n 个整数,第 i 个整数表示序列的第 i 个数字 ai

3|3输出格式

输出一行一个整数表示答案。

3|4样例 #1

1|0样例输入 #1

7 2 -4 3 -1 2 -4 3

1|0样例输出 #1

4

3|5提示

1|0样例 1 解释

选取 [3,5] 子段 {3,1,2},其和为 4

1|0数据规模与约定

  • 对于 40% 的数据,保证 n2×103
  • 对于 100% 的数据,保证 1n2×105104ai104

3|6题解

令f[i]为以i结尾最大的字段和
f[i]=max(f[i-1]+a[i],a[i])
最后ans即为最大的f[i]

3|7Code

#include<bits/stdc++.h> #define ll long long using namespace std; const int N=1e6+10; ll ans=-1e9,n,a[N],f[N]; inline ll read() { ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int main() { //freopen(".in","r",stdin); //freopen(".out","w",stdout); n=read(); for(int i=1;i<=n;i++) { a[i]=read(); f[i]=max(a[i],f[i-1]+a[i]); ans=max(f[i],ans); } cout<<ans; return 0; } //**月雩·薇嫭**

4|0[蓝桥杯 2020 省 AB1] 走方格

4|1题目描述

在平面上有一些二维的点阵。

这些点的编号就像二维数组的编号一样,从上到下依次为第 1 至第 n 行,从左到右依次为第 1 至第 m 列,每一个点可以用行号和列号来表示。

现在有个人站在第 1 行第 1 列,要走到第 n 行第 m 列。只能向右或者向下走。

注意,如果行号和列数都是偶数,不能走入这一格中。

问有多少种方案。

4|2输入格式

输入一行包含两个整数 nm

4|3输出格式

输出一个整数,表示答案。

4|4样例 #1

1|0样例输入 #1

3 4

1|0样例输出 #1

2

4|5提示

1n,m30

蓝桥杯 2020 第一轮省赛 A 组 G 题(B 组 H 题)。

4|6题解

很容易想到f[i][j]=f[i-1][j]+f[i][j-1]
再加上限定条件即可
即判断f[i-1][j]/f[i][j-1]是否可取

4|7Code

#include<bits/stdc++.h> #define ll long long using namespace std; const int N=30+10; ll n,m,f[N][N],vis[N][N]; inline ll read() { ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int main() { //freopen(".in","r",stdin); //freopen(".out","w",stdout); n=read(),m=read(); for(int i=2;i<=n;i+=2) for(int j=2;j<=m;j+=2) vis[i][j]=-1; f[1][1]=1; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(vis[i][j]!=-1) { f[i+1][j]+=f[i][j]; f[i][j+1]+=f[i][j]; } cout<<f[n][m]; return 0; } //**月雩·薇嫭**

5|0[USACO1.5] [IOI1994]数字三角形 Number Triangles

5|1题目描述

观察下面的数字金字塔。

写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。

在上面的样例中,从 73875 的路径产生了最大权值。

5|2输入格式

第一个行一个正整数 r ,表示行的数目。

后面每行为这个数字金字塔特定行包含的整数。

5|3输出格式

单独的一行,包含那个可能得到的最大的和。

5|4样例 #1

1|0样例输入 #1

5 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5

1|0样例输出 #1

30

5|5提示

【数据范围】
对于 100% 的数据,1r1000,所有输入在 [0,100] 范围内。

题目翻译来自NOCOW。

USACO Training Section 1.5

IOI1994 Day1T1

5|6题解

与上一道题类似
易得f[i][j]=max(f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j])

5|7Code

#include<bits/stdc++.h> #define ll long long using namespace std; const int N=1e3+10; int n,f[N][N],ans; inline ll read() { ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int main() { //freopen(".in","r",stdin); //freopen(".out","w",stdout); n=read();int a; for(int i=1;i<=n;i++) { for(int j=1;j<=i;j++) { a=read(); f[i][j]=max(f[i-1][j]+a,f[i-1][j-1]+a); } } for(int i=1;i<=n;i++)ans=max(f[n][i],ans); cout<<ans; return 0; } //**月雩·薇嫭**

6|0[NOIP1999 普及组] 导弹拦截

6|1题目描述

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度,计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

6|2输入格式

一行,若干个整数,中间由空格隔开。

6|3输出格式

两行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

6|4样例 #1

1|0样例输入 #1

389 207 155 300 299 170 158 65

1|0样例输出 #1

6 2

6|5提示

对于前 50% 数据(NOIP 原题数据),满足导弹的个数不超过 104 个。该部分数据总分共 100 分。可使用O(n2) 做法通过。
对于后 50% 的数据,满足导弹的个数不超过 105 个。该部分数据总分也为 100 分。请使用 O(nlogn) 做法通过。

对于全部数据,满足导弹的高度为正整数,且不超过 5×104

此外本题开启 spj,每点两问,按问给分。


upd 2022.8.24:新增加一组 Hack 数据。

6|6题解

Problem 1:
建立一个栈,如果当前a[i]小于等于栈顶元素,则将栈数量+1,栈顶变成a[i]
如果栈顶大于a[i],那么找到栈中第一个≤a[i]的元素,将它替换成a[i]
(可以模拟一下,这样可以保证答案正确,但数组中序列可能不正确)
Problem 2:
考虑贪心算法,f[i]表示第i个系统最小的元素
并且f[len]是f中最大的元素
如果当前元素a[i]大于flen,那么只能新增一个系统来存a[i],即f[++len]=a[i]
反之,寻找第一个大于等于a[i]的f[x],令f[x]=a[i](第x个系统最小的变为a[i])

6|7Code

#include<bits/stdc++.h> #define ll long long using namespace std; const int N=2e5+10; int n,a[N],Len,f[N]; inline ll read() { ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int main() { //freopen(".in","r",stdin); //freopen(".out","w",stdout); while(scanf("%d",&a[++n])!=EOF); n--; f[0]=1e9; for(int i=1;i<=n;i++) { if(a[i]<=f[Len])f[++Len]=a[i]; else { int l=1,r=Len; while(l<r) { int mid=(l+r)>>1; if(f[mid]>=a[i])l=mid+1; else r=mid; } f[l]=a[i]; } } cout<<Len<<endl; memset(f,0,sizeof(f));Len=0; f[0]=-1e9; for(int i=1;i<=n;i++) { if(a[i]>f[Len])f[++Len]=a[i]; else { int l=1,r=Len; while(l<r) { int mid=(l+r)>>1; if(a[i]<=f[mid])r=mid; else l=mid+1; } f[l]=a[i]; } } cout<<Len; return 0; } //**月雩·薇嫭**

__EOF__

本文作者月雩·薇嫭
本文链接https://www.cnblogs.com/yueyuweihu/p/17806564.html
关于博主:我悲叹伤者。终成己。
版权声明:未经允许禁止转载
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   月雩·薇嫭  阅读(43)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示