\(NOI\) 模拟赛

字符串滚出 \(OI\)

看到题目名称,\(T1\) 串,\(T2\) 两个串,\(T3\) K个串,我 \(\cdots\),血压已经上来了。

\(T1\) 写了 \(O(n^2)\) 的暴力15pts,正解是AC自动机上面做dp???我还是太菜了,dp没看懂。

\(T2\) 总觉得是在kmp上面修改,但被自己hack了,又加了个小数据跑暴力的部分(貌似数据没有在小数据上卡一般的kmp)40pts。正解是拿出了一个神奇的柿子加上FFT???

\(T3\) \(O(n^2)\) 暴力写挂了,正解是主席树加堆 ,有想过主席树但是被自己否定了。


update on 21.6.22

发现是BZOJ原题,就不开隐藏了,当题解写一下,放一下代码。

T1:串

这篇Blog写的比老师给的易懂(逃。所以写的是这篇里的思想和我的理解。对字符串不是很会,所以很多地方要感性理解一下。

题意:

给出一个字符串集,求有多少字符串可拆分为非空的两部分,每部分都是集合中某一个字符串的前缀。

题解:

考虑枚举所有不重复的前缀拼接,总前缀数为 \(m\),如果把重复的都计算,那么答案为 \(m^2\),于是考虑算出重复的字符串数。

如果一个字符串有重复,那么它一定可以有多种分解方式。如:

有 \((head,a)+(a,end)\) 和 \((haed,b)+(b,end)\) 两种分解。拼接的是前缀,所以我们可以判断 \((head,a),(a,end),(haed,b),(b,end),(a,b)\) 都是出现在字符串集中的前缀,其中 \((a,b)\) 既是 \((head,b)\) 的后缀,又是 \((a,end)\) 的前缀。由于 AC 自动机上的 fail 数组正是 AC 自动机上存在的最长后缀,方便处理题中的后缀,所以把所有的字符串都丢到 AC 自动机上面。考虑枚举 \((a,end)\),那么它的 fail 就是它的最长后缀,此时即为最近的 b。可以预处理出以 \((a,b)\) 为后缀的字符串数,这就是以 \((a,b)\) 为中间串,\((b,end)\) 为后面串的贡献。枚举所有分割点 \(a,b,c,d,e,\cdots\) 都这样操作,除了最后一个 fail=0 的保留,刚好可以把所有重复的字符串去除。因为只要这个分割点 fail 不等于 0,就说明这种分割方式一定可以被后面 fail 的方式替代。

在实际操作中,我们只需要枚举 \((a,end)\) (也就是所有 fail 不为 0 的节点),它的 fail 为 \((b,end)\) ,那么沿着 a 的 fa 向上找 len[\((b,end)\)] 次后就表示\((a,b)\),减去以它为后缀的前缀数量。用字典树大小的平方减去所有贡献后即为最终答案。

代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
string c[10005];
int ch[300005][30],cn=0;
int fail[300005],cnt[300005],dis[300005],fa[300005];//cnt即以它为后缀的前缀数量
void insert(string s){
int len=s.length(),p=0;
for(int i=0;i<len;i++){
int t=s[i]-'a'+1;
if(!ch[p][t])
ch[p][t]=++cn;
dis[ch[p][t]]=dis[p]+1;
fa[ch[p][t]]=p;
p=ch[p][t];
}
}
void getfail(){
queue<int> q;
int p=0;
for(int i=1;i<=26;i++){
if(ch[p][i]){
q.push(ch[p][i]);
fail[ch[p][i]]=0;
}
}
while(!q.empty()){
p=q.front();
q.pop();
for(int i=1;i<=26;i++){
if(!ch[p][i])continue;
int v=ch[p][i],j;
for(j=fail[p];j&&!ch[j][i];j=fail[j]);
fail[v]=ch[j][i];
for(j=fail[v];j;j=fail[j])
cnt[j]++;
q.push(v);
}
}
}
int up(int now,int lenth){
while(lenth--)now=fa[now];
return now;
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>c[i];
insert(c[i]);
}
getfail();
int ans=cn*cn;
for(int i=1;i<=cn;i++)
if(fail[i])ans-=cnt[up(i,dis[fail[i]])];
cout<<ans;
return 0;
}

T2:两个串

学了FFT再来看。


T3:K个串

题意:

求第K大的子串和。(以下和均为重复只算一次)

题解:

考虑每个点作为左端点对应的答案,将1为左端点时的答案:\(1\) 到 \(i\) 的和\((1\leq i\leq n)\),建一棵线段树,可以维护最大值。对于2为左端点的答案,发现只需要在第一颗线段树的基础上,将1节点改为-inf,并在[2,next[1]-1](next[i]表示i之后第一次出现a[i]的位置,没有出现则记为n+1)的节点全部减掉a[1]。后面的线段树都可以像这样构建,于是用可持久化线段树,有区间修改所以再写一个懒标记。

维护出每棵树后将他们的最大值插进堆里,取出其中最大值后,在对应线段树中删去最大值,也就是将对应节点改为-inf,再将此时的最大值插入堆中,循环k次就可以找到第K大值了。

时间复杂度大概是\(O((n+k)\log n)\),神犇hrj对它的常数略有研究,%%%。

代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,k;
const int maxn=100005;
const int maxk=200005;
map<int,int> M;//仅用于求和时判重
int a[maxn],next[maxn];
int f[maxn];
priority_queue<pair<int,int> >Q;
//segment_tree----
#define ls(x) t[x].ls
#define rs(x) t[x].rs
#define mx(x) t[x].mx
#define tag(x) t[x].tag
int rt[maxn],tn=1;
struct node{
int ls,rs;
int mx,tag;
}t[maxn<<8];
int built(int l,int r,int p){//初始线段树
if(l==r){mx(p)=f[l];return p;}
int mid=(l+r)>>1;
ls(p)=built(l,mid,++tn);
rs(p)=built(mid+1,r,++tn);
mx(p)=max(mx(ls(p)),mx(rs(p)));
return p;
}
void cover(int &x,int d){//在x基础上新建节点
int y=++tn;
ls(y)=ls(x);
rs(y)=rs(x);
mx(y)=mx(x)+d;
tag(y)=tag(x)+d;
x=y;
}
void pushdown(int p){
if(!tag(p))return ;
cover(ls(p),tag(p));
cover(rs(p),tag(p));
tag(p)=0;
}
void make(int &now,int pre,int l,int r,int ml,int mr,int d){//now为新节点,pre为基础,l,r为范围,ml,mr为修改范围,d为加上的值,以这些数据新建线段树
now=++tn;
ls(now)=ls(pre);
rs(now)=rs(pre);
mx(now)=mx(pre);
tag(now)=tag(pre);
if(ml>mr)return ;
if(l>=ml&&r<=mr){
mx(now)+=d;
tag(now)+=d;
return ;
}
pushdown(now);
int mid=(l+r)>>1;
if(ml<=mid)make(ls(now),ls(now),l,mid,ml,mr,d);
if(mr>mid)make(rs(now),rs(now),mid+1,r,ml,mr,d);
mx(now)=max(mx(ls(now)),mx(rs(now)));
}
int getposition(int p,int l,int r){//找到这棵线段树中最大值的位置
if(l==r)return l;
int mid=(l+r)>>1;
if(mx(ls(p))>mx(rs(p)))return getposition(ls(p),l,mid);
else return getposition(rs(p),mid+1,r);
}
//----------------
signed main(){
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
f[i]=f[i-1];
if(M.count(a[i]))next[M[a[i]]]=i;
else f[i]+=a[i];
M[a[i]]=i;
}
rt[1]=1;
built(1,n,1);
Q.push(make_pair(mx(rt[1]),1));
for(int i=2;i<=n;i++){
make(rt[i],rt[i-1],1,n,i,next[i-1]?next[i-1]-1:n,-a[i-1]);
make(rt[i],rt[i],1,n,i-1,i-1,0xb0b0b0b0b0b0b0b0LL);
Q.push(make_pair(mx(rt[i]),i));
}
int maxa;int kth;
for(int i=1;i<=k;i++){
maxa=Q.top().first;
kth=Q.top().second;
Q.pop();
int p=getposition(rt[kth],1,n);
make(rt[kth],rt[kth],1,n,p,p,-100000000000000000LL);
Q.push(make_pair(mx(rt[kth]),kth));
}
printf("%lld",maxa);
return 0;
}

21.6.21 test的更多相关文章

  1. 十二星座 英文名:Aries 金牛座 (4/21 - 5/20)的英文名: Taurus 双子座 (5/21 - 6/21)的英文名: Gemini 巨蟹座 (6/22 - 7/22)的英文名: Cancer 狮子座 (7/23 - 8/22)的英文名: Leo 处女座/室女座 (8/23 - 9/22)的英文名: Virgo 天秤座 (9/2

    十二星座的具体顺序是:白羊座(Aries).金牛座(Taurus).双子座(Gemini).巨蟹座(Cancer).狮子座(Leo).处女座(Virgo).天秤座(Libra).天蝎座(Scorpio ...

  2. Android View事件机制 21问21答

    原文: http://www.cnblogs.com/punkisnotdead/p/5179115.html#3358859 1.View的坐标参数 主要有哪些?分别有什么注意的要点? 答:Left ...

  3. 【Android】3.21 示例21—兴趣点收藏功能

    分类:C#.Android.VS2015.百度地图应用: 创建日期:2016-02-04 简介:介绍如何创建.管理本地收藏的兴趣点数据 详述: (1)新建本地点收藏: (2)查看已收藏本地点: (3) ...

  4. 【转】ORACLE SQL基础—DDL语言 礼记八目 2017-12-23 21:26:21

    原文地址:https://www.toutiao.com/i6502733303550837261/ SQL语言分为:DDL数据定义语言,DML数据操纵语言,DCL是数据库控制语言,TC事务控制语言 ...

  5. 记录21.07.21 —— ES6基础

    学习目录 课件地址: ES6核心技术课件 1.let关键字 1.1 let与var的区别 ①let不能重复定义 ②块作用域的区别 ③变量声明之前let不能使用,var可以 ④ 课件代码 <htm ...

  6. 哈夫曼树-Fence Repair 分类: 树 POJ 2015-08-05 21:25 2人阅读 评论(0) 收藏

    Fence Repair Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 32424 Accepted: 10417 Descri ...

  7. [转自老马的文章]用MODI OCR 21种语言

    作者:马健邮箱:stronghorse_mj@hotmail.com发布:2007.12.08更新:2012.07.09按照<MODI中的OCR模块>一文相关内容进行修订2012.07.0 ...

  8. 用MODI OCR 21种语言

    作者:马健邮箱:stronghorse_mj@hotmail.com发布:2007.12.08更新:2012.07.09按照<MODI中的OCR模块>一文相关内容进行修订2012.07.0 ...

  9. Java 学习(21):Java 实例

    Java 实例 本章节我们将为大家介绍 Java 常用的实例,通过实例学习我们可以更快的掌握 Java 的应用. Java 环境设置实例 //HelloWorld.java 文件 public cla ...

随机推荐

  1. Vue中使用 iview 之-踩坑日记

    导航列表: 一.iview单选框Select验证问题 二.iview表单v-if引起的问题 三.Upload 手动上传组件 使用是出现的问题 四.Tabs嵌套使用时的问题 五.Tooltip 换行问题 ...

  2. Maven专题3——生命周期与插件

    三套生命周期 Maven有3套相互独立的生命周期,用户可以调用某个生命周期的阶段,而不会对其他生命周期产生影响. 每个生命周期包含一些有先后顺序的阶段,后面的阶段依赖于前面的阶段,意味着用户调用后面的 ...

  3. 聊一聊开闭原则(OCP).

    目录 简述 最早提出(梅耶开闭原则) 重新定义(多态开闭原则) 深入探讨 OCP的两个特点 对外扩展开放(Open for extension) 对内修改关闭 抽象 关闭修改.对外扩展? 简述 在面向 ...

  4. 计算机网络 -- TCP/IP

    画图标准 OSI七层模型 7.应用层 作用:为用户提供软件/接口/界面 interface 协议:OICQ.HTTP.HTTPS.BT/P2P 6.表示层 作用:用于对用户数据进行数据呈现.(数据格式 ...

  5. 学习PHP中的国际化日期格式化操作

    对于国际化功能来说,日期相关的格式化操作也是一块重头戏,毕竟不同的时区,不同的国家对于日期的表示方式都会有些不同.今天我们主要来学习的就是国际化地表示日期相关的信息内容. 日期格式化 首先就是最直接的 ...

  6. PHP中的MySQLi扩展学习(二)mysqli类的一些少见的属性方法

    虽说是少见的一些属性方法,但是可能还是有不少同学在日常的开发中使用过,这里只是学习了可能相对来说我们用得比较少的一些 mysqli 的属性或方法.就当是扩展一下自己的知识体系. 切换用户 首先就是切换 ...

  7. 学习PDO中的错误与错误处理模式

    在 PDO 的学习过程中,我们经常会在使用事务的时候加上 try...catch 来进行事务的回滚操作,但是大家有没有注意到默认情况下 PDO 是如何处理错误语句导致的数据库操作失败问题呢?今天,我们 ...

  8. Jmeter扩展组件开发(10) - 自定义扩展函数助手的开发

    CODE package com.functions;import org.apache.jmeter.engine.util.CompoundVariable;import org.apache.j ...

  9. 利用Qt中的ui文件生成PyQt5程序,自定义槽函数

    1.在Qt Creator4.8.0上面设计如上.ui文件 2.点击上方图标,可以建立信号-槽连接,button_click()为自定义槽函数 3.设计目的:点击clear按钮,可消除上方文本框中的内 ...

  10. 『Python』列表生成式、生成器与迭代器

    1. 迭代 在 Python中, 迭代是通过 for ... in 来完成的, 而很多语言比如 C 语言, 迭代 list 是通过下标完成的. Python 的 for 循环抽象程度要高于 C 的 f ...