植物收集

题面:

Dr. Wang是一位植物领域的专家。他要给他的学生们上一节课。课堂上需要展示一种植物。众所周知,植物的生长是有阶段的,本着严谨科学的态度,Dr. Wang 希望可以在课堂上给学生们展示该植物的每个生长阶段。
Dr. Wang要讲授的植物有n个阶段,现在他需要弄到该植物每种阶段各一株。他打听到了这种植物每个生长阶段的价格。但由于科研经费不足,有时候直接购买并不是一个好选择。所以他计划用上他的催熟科技。具体的,Dr. Wang可以进行如下两种操作:

  • 以$a_i$的价格购买一株生长到第$i$个阶段的植物。
  • 花费$k$的代价使用催熟科技,将所有已购买的植物生长阶段增加$1$。

若一株植物已经到了阶段$n$,则返回阶段$1$可以理解为成熟到只剩种子,然后被重新种下去了)。现在Dr. Wang想让你帮忙求出,他最少需要花费多少代价,可以收集到植物的每个生长阶段。

题解:

因为阶段$n$的植物会返回阶段$1$,所以破环为链,下文所有$n$均视为$2n$

\(n^2\)做法(\(80\)pts):

观察发现,经过$k$次操作二后,阶段$i$植物的价值可看作:$$min_{j=i-k}^i a_j$$,可使用st表维护区间最小值,然后枚举操作二的次数,取$min$即可。

\(n \log n\)做法(\(100\)pts):

将$n^2$做法中枚举的最终代价输出,注意到代价是一个关于$k$的单谷函数,三分即可。

证明:

考虑$m \rightarrow m+1$时,每轮购买植物能节省的钱是单调不增的,但每轮使用科技都是增加$k$,所以二者相加必定会存在峰值,且两个方向均单调不增。

Code:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
#define ll long long
#define inf 0x7f7f7f7f
int n,k,a[N<<1],st[N<<1][21],lg[N<<1],l,r,n1;
ll ans;
void init(){
memset(st,inf,sizeof(st));
for(int i=1;i<=n;i++)lg[i]=lg[i-1]+(1<<lg[i-1]==i);
for(int i=1;i<=n;i++)st[i][0]=a[i];
for(int j=1;(1<<j)<=n;j++){
for(int i=1;i+(1<<j)-1<=n;i++){
st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
}
}
ll check(int x){
ll res=0;
for(int i=x+1;i<=n1+x;i++){
int p=lg[x+1]-1;
res+=min(st[i-x][p],st[i-(1<<p)+1][p]);
}
return res;
}
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
// freopen("collect.in","r",stdin);
// freopen("collect.out","w",stdout);
cin>>n>>k;
l=0,r=n-1,n1=n;
for(int i=1;i<=n;i++){
cin>>a[i];
a[i+n]=a[i];
ans+=a[i];
}
n<<=1;
init();
while(l<r){
int lmid=l+(r-l)/3,rmid=l+(r-l)/3*2;
if(lmid==rmid)break;
if(check(lmid)+1LL*k*lmid>check(rmid)+1LL*k*rmid)l=lmid;
else r=rmid;
}
for(int i=l;i<=r;i++){
ll t=check(i)+1LL*k*i;
ans=min(t,ans);
}
cout<<ans;
return 0;
}

美丽子区间

题面:

小\(Z\)喜欢区间。

小\(Z\)定义一个区间是美丽的,当且仅当这个区间的最大值或最小值不出现在区间的开头和结尾。如\([3,2,4,1]\)不是一个美丽的区间,因为最小值\(1\)出现在了结尾;而\([2,4,1,3]\)是一个美丽的区间,因为\(1,4\)没有出现在开头或结尾。

小\(Z\)有一个排列,现在需要求出这个序列中有多少个子区间是美丽的。

题解:

\(n^2\)做法(\(40\)pts):

st表预处理出所有区间最大值和最小值,暴力枚举所有长度大于等于$4$的子区间判断是否合法

\(n \log n\)做法(\(100\)pts):

考虑容斥,优美的定义是最大值和最小值都不出现在开头或结尾,这个条件难以限制,可以从容斥的角度考虑。优美区间的数量为:总区间数$-$最大值在开头的子区间数量$-$最小值在开头的子区间数量$-$最大值在结尾的子区间数量$-$最小值在结尾的子区间数量。

之后我们会发现如果一个子区间的最大值出现在开头且最小值出现在结尾,这样的子区间被我们减去了两次,出现了减多了的情况。因此我们需要额外加回来一些区间,即加上:最大值在开头且最小值在结尾的子区间数量$+$最大值在开头且最小值在结尾的子区间数量。

求最大值在开头的子区间数量,可以使用单调栈,求每个数右边第一个大于该数的位置;如对于$p_i$,其右边第一个大于他的位置在$r_i$,那么$\forall j \in [i,r_i)$,子区间$[i,j]$都是最大值在开头的子区间。最小值在开头的子区间等同理可求。

之后求最大值在开头且最小值在结尾的子区间数量,继续考虑单调栈;根据上面的分析,$\forall j \in [i,r_i)$,子区间$[i, j]$都是最大值在开头的子区间,那需要求有多个$j$满足$p_j$是$[i, j]$的最小值。

不难发现这样的$j$,可以被一个从后往前加入元素的单调栈维护出来。只需要再单调栈中二分有多少个$j$满足$j < r_i$即可

  • 若使用模拟单调栈,注意边界和初始化。
  • 注意长度为一的子区间会被重复减去$4$次,所以最后输出的结果为$ans+3*n$
Code:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define inf 0x7f7f7f7f
#define ll long long
const int N=2e5+5;
int n,a[N];
int lmin[N],lmax[N],rmin[N],rmax[N];
int w[N],s[N],p,l,r;
void work(){
memset(s,0,sizeof(s));
a[n+1]=inf;p=1;s[p]=1;
for(int i=2;i<=n+1;i++){
if(a[i]<a[s[p]])s[++p]=i;
else {
while(a[i]>a[s[p]]&&p)rmax[s[p--]]=i;
s[++p]=i;
}
}
memset(s,0,sizeof(s));
a[n+1]=0;p=1;s[p]=1;
for(int i=2;i<=n+1;i++){
if(a[i]>a[s[p]])s[++p]=i;
else {
while(a[i]<a[s[p]]&&p)rmin[s[p--]]=i;
s[++p]=i;
}
}
memset(s,0,sizeof(s));
a[0]=inf;p=1;s[p]=n;
for(int i=n-1;i>=0;i--){
if(a[i]<a[s[p]])s[++p]=i;
else {
while(a[i]>a[s[p]]&&p)lmax[s[p--]]=i;
s[++p]=i;
}
}
memset(s,0,sizeof(0));
a[0]=0;p=1,s[p]=n;
for(int i=n-1;i>=0;i--){
if(a[i]>a[s[p]])s[++p]=i;
else{
while(a[i]<a[s[p]]&&p)lmin[s[p--]]=i;
s[++p]=i;
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
//freopen("interval.in","r",stdin);
//freopen("interval.out","w",stdout);
cin>>n;
ll ans=1LL*n*(n+1)/2;
for(int i=1;i<=n;i++)cin>>a[i];
work();
for(int i=1;i<=n;i++)ans-=rmin[i]+rmax[i]-lmin[i]-lmax[i];
memset(s,0,sizeof(s));
p=1,s[p]=n;
for(int i=n-1;i>0;i--){
if(a[i]>a[s[p]]){
s[++p]=i;
l=1,r=p;
while(l<r){
int mid=(l+r)>>1;
if(s[mid]>rmax[i])l=mid+1;
else r=mid;
}
ans+=p-r;
}else{
while(a[i]<a[s[p]]&&p)--p;
s[++p]=i;l=1,r=p;
while(l<r){
int mid=(l+r)>>1;
if(s[mid]>rmax[i])l=mid+1;
else r=mid;
}
ans+=p-r;
}
}
memset(s,0,sizeof(s));
p=1,s[p]=1;
for(int i=2;i<=n;i++){
if(a[i]>a[s[p]]){
s[++p]=i;
l=1,r=p;
while(l<r){
int mid=(l+r)>>1;
if(s[mid]<lmax[i])l=mid+1;
else r=mid;
}
ans+=p-r;
}else{
while(a[i]<a[s[p]])p--;
s[++p]=i;l=1,r=p;
while(l<r){
int mid=(l+r)>>1;
if(s[mid]<lmax[i])l=mid+1;
else r=mid;
}
ans+=p-r;
}
}
cout<<ans+3*n;
return 0;
}

字符序列

题面:

小\(Z\)喜欢字符串。

小\(Z\)定义了一个奇妙的函数\(f(c, s)\),对于一个长度为\(n\)的字符串\(f(c, s) = cs_1cs_2c . . . cs_nc\),其中 c 是一个小写英文字母。

不难发现这个函数的作用就是在\(s\)的每一个空位插入一个小写英文字母,如\(f(c, aba) =cacbcac\)。

小\(Z\)通过这个函数构造了使用了\(n\)个小写字母\(c_1, c_2 . . . c_n\)构造\(n\)个字符串\(s_1, s_2, . . . s_n\),其中 \(s_i = f(c_i, s_i−1)\),其中\(s_0\)空串。

小\(Z\)也非常喜欢子序列,现在他想知道\(s_n\)中有多少个本质不同的非空子序列。

题解:

$\vert \sum \vert$表示字符集大小

\(2^n\)做法(\(50\)pts)

考虑dp,设$f_i$为前i个字符组成的非空子序列个数,$last_{i,c}$为i前面的上个字符c的位置,容易得出状态转移方程(c为当前字符,$last_c$数组为滚动数组):
$$\begin{cases}
f_i=f_{i-1}*2+1&last_c=0\\
f_i=f_{i-1}*2-f_{last_c-1}&last_c \neq 0\\
\end{cases}$$
将字符串展开,dp计数即可。

\(n{\vert \sum \vert}^3\)做法:(\(100\)pts)

解法一中的方程难以优化,考虑重构dp方式,设$f_{i,c}$为前i个字符组成的以字符c结尾的非空子序列数量,由此推出转移方程($s_i$为位置i的字符):
$$
\begin{cases}
f_{i,c} = f_{i-1,c} & c \neq s_i\\
f_{i,c} = \sum_{j=1}^{\vert \sum \vert} f_{i-1,j}+1& c=s_i\\
\end{cases}
$$
该dp复杂度为$O(2^n\vert\sum\vert)$,考虑优化,发现该dp可写成矩阵形式,对角线变为一,转移矩阵中对应字母所在的列变为一:
$$
\begin{bmatrix}0&0&\cdots&0&0&1\end{bmatrix}\times\begin{bmatrix}1&0&1&0&\cdots&0\\0&1&1&0&\cdots&0\\0&0&1&0&\ddots&0\\0&0&1&1&\ddots&0\\\vdots&\vdots&\vdots&\vdots&\ddots&\vdots\\0&0&1&0&\cdots&1\\\end{bmatrix}\times\begin{bmatrix}\cdots&\cdots&\cdots&\cdots&\cdots&\cdots\\\vdots&\vdots&\vdots&\vdots&\vdots&\vdots\\\ddots&\ddots&\ddots&\ddots&\ddots&\ddots\\\vdots&\vdots&\vdots&\vdots&\vdots&\vdots\\\cdots&\cdots&\cdots&\cdots&\cdots&\cdots\\\end{bmatrix}\times\cdots
$$

观察发现所得字符串对称,设$T_i$为从操作i执行到操作n获得的字符串,发现$T_i=T_{i+1}+s_i+T_{i+1}$,由此从n至1逆序合并矩阵,并输出矩阵最后一行的和即可

Code:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int n;string s;
struct sqr{
long long a[30][30];
void init(){
for(int i=0;i<=26;i++)a[i][i]=1;
}
sqr(){
memset(a,0,sizeof(a));
}
sqr operator*(const sqr &x)const{
sqr z;
for(int k=0;k<=26;k++){
for(int i=0;i<=26;i++){
for(int j=0;j<=26;j++){
z.a[i][j]=(z.a[i][j]+a[i][k]*x.a[k][j])%mod;
}
}
}
return z;
}
}res;
sqr insert(int x){
sqr f;
f.init();
for(int i=0;i<=26;i++)f.a[i][x]=1;
return f;
}
int main(){
//freopen("subseq.in","r",stdin);
//freopen("subseq.out","w",stdout);
cin>>n;
cin>>s;
s=' '+s;
long long ans=0;
res.init();
for(int i=n;i>=1;i--)res=res*insert(s[i]-'a')*res;
for(int i=0;i<26;i++)ans=(ans+res.a[26][i])%mod;
cout<<ans;
return 0;
}

结束喽(T4不会)

NOIP 模拟赛(10.10):植物收集,美丽子区间,字符序列的更多相关文章

  1. NOIP模拟赛-2018.10.22

    模拟赛 今天第一节课是历史,当然是不可能上的,一来到机房发现今天高二考试... 老师说以后可能还要给高一考...那还不如现在跟着做好了,毕竟在学长学姐中垫底显得没那么丢人 这套题风格挺奇怪的...为什 ...

  2. NOIP模拟赛 17.10.10

    初次见面(firstmeet)[题目背景]雾之湖边,静得可怕.露米娅出神凝望.黑白连衣裙,像极了绽放的墨黑和洁白的莲.身边的雾之湖,倒映着血色天空.酒红的双眸,映照一切.低声浅笑,双臂伸直,她悄无声息 ...

  3. noip模拟赛(10.4) 序列(sequence)

    序列(sequence) [题目描述] 给定一个1~n的排列x,每次你可以将x1~xi翻转.你需要求出将序列变为升序的最小操作次数.有多组数据. [输入数据] 第一行一个整数t表示数据组数. 每组数据 ...

  4. noip模拟赛(10.4) 背包(pack)

    [题目描述] 蛤布斯有n种商品,第i种物品的价格为ai,价值为bi.有m个人来向蛤布斯购买商品,每个人每种物品只能购买一个.第j个人有cj的钱,他会不停选择一个能买得起的价格最高的商品买走(如果有多个 ...

  5. noip模拟赛(10.4) 字典序(dictionary)

    [题目描述] 你需要构造一个1~n的排列,使得它满足m个条件,每个条件形如(ai,bi),表示ai必须在bi前面.在此基础上,你需要使它的字典序最小. [输入数据] 第一行两个正整数n,m.接下来m行 ...

  6. 水(NOIP模拟赛Round #10)

    题目描述: 小Z有一个长度为的数列.他有次令人窒息的操作,每次操作可以使某个数字或.他当然是希望这些数字的乘积尽量小了.为了简化题目,你只需输出操作完成后的数列即可. ———————————————— ...

  7. 题(NOIP模拟赛Round #10)

    题目描述: 有一张的地图,其中的地方是墙,的地方是路.有两种操作: 给出个地点,询问这个地点中活动空间最大的编号.若询问的位置是墙,则活动空间为:否则活动空间为询问地点通过四联通能到达的点的个数.如果 ...

  8. 大(NOIP模拟赛Round #10)

    题目描述: 小Z有个n个点的高清大图,每个点有且只有一条单向边的出边.现在你可以翻转其中的一些边,使他从任何一个点都不能通过一些道路走回这个点.为了方便,你只需输出方案数对取模即可.当在两个方案中有任 ...

  9. 10.17 NOIP模拟赛

    目录 2018.10.17 NOIP模拟赛 A 咒语curse B 神光light(二分 DP) C 迷宫maze(次短路) 考试代码 B 2018.10.17 NOIP模拟赛 时间:1h15min( ...

  10. 10.16 NOIP模拟赛

    目录 2018.10.16 NOIP模拟赛 A 购物shop B 期望exp(DP 期望 按位计算) C 魔法迷宫maze(状压 暴力) 考试代码 C 2018.10.16 NOIP模拟赛 时间:2h ...

随机推荐

  1. mybatis - [05] Mybatis的CURD

    数据库:mysql 8.0.28 技术框架:mybatis 3.5.13.maven 3.8.7 一.准备工作 (1)数据库建库建表 -- 创建数据库 create database if not e ...

  2. Linux - 禁ping & 开放访问端口

    适用于Linux操作系统 禁ping 1.编辑 /etc/sysctl.conf 文件,文件末尾增加如下内容后,保存退出. net.ipv4.icmp_echo_ignore_all = 1 2.在命 ...

  3. FastAPI路由与请求处理进阶指南:解锁企业级API开发黑科技 🔥

    title: FastAPI路由与请求处理进阶指南:解锁企业级API开发黑科技 date: 2025/3/3 updated: 2025/3/3 author: cmdragon excerpt: 5 ...

  4. sql---一条sql查询语句是如何查询的、更新的

    mysql分为server层和层存储引擎层 类型:InnoDB.MyISAM 查询过程 连接器:与客户端建立连接.获得权限.维持和管理命令 查询缓存:弊大于利,在8.0之后已去除 分析器:对sql语法 ...

  5. 【检索类型EI、Scopus】第二届智能计算与数据分析国际学术会议(ICDA 2025)

    为探讨数据科学和计算智能领域的关键问题,促进相关交流,由黄河科技学院主办的2025年第二届智能计算与数据分析国际学术会议(ICDA 2025)将于2025年8月22日-24日在中国郑州召开.本届会议拟 ...

  6. DeepSeek+Claude强强联手,使用AI驱动DjangoStarter 3.1框架升级

    前言 上个月底培训了一周就没时间更新博客 上周有一些空闲时间,就继续体验最近很火的AI辅助编程 之前的文章中有说到我配置好了 VSCode + Cline 插件搭配本地部署的 DeepSeek 来实现 ...

  7. 实现领域驱动设计 - 使用ABP框架 - 领域服务

    领域服务 领域服务实现领域逻辑 依赖于服务和存储库. 需要处理多个聚合,因为该逻辑不适合任何聚合. 领域服务与领域对象一起工作.它们的方法可以获取并返回实体.值对象.原始类型--但是,它们不获取/返回 ...

  8. Git 覆盖刚刚 commit 的 message

    场景重现 通常噼里啪啦键盘一段猛搓后(写代码啊),然后会 git add . git commit -m "modify semo" # 注意上面 semo 应该是 some,发现 ...

  9. python练习-爬虫

    场景: 1.网址hppt://xxx.yyy.zzz.cn2.打开网页后显示 : 3.填上姓名 身份证和验证码,点击查询后,返回查询结果. 4.页面有cookie. 方案一: 程序中嵌入浏览器根据网址 ...

  10. 搞笑的ini文件读取ReadSectionValues

    用fdconnection自动生成连接文本,将其写入ini文件,用于程序的读写.ini文件见后面. Lfini.ReadSection('LocalConnection',lstrings); Sho ...