植物收集

题面:

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. 超详细的 Linux - conda 环境安装教程

    一.引言 在 Linux 系统中安装 conda 环境可以为您提供便捷的 Python 环境管理和包管理功能.本教程将详细介绍在 Linux 系统中安装 conda 的步骤. 二.安装前的准备 确保您 ...

  2. 晶振测试仪GDS-80系列参数

    晶振测试仪GDS-80系列 一.产品简介 晶振测试仪GDS-80系列是高性价比的晶振测试系统,采用网络分析技术,实现智能化测量,符合IEC-444标准.测量频率范围10KHz-200KHz,1MHz- ...

  3. ant design pro 使用 getFieldValue、setFieldsValue

    getFieldValue 获取表单指定 name 值,setFieldsValue 为表单指定 name 设定值 import type { ProFormInstance } from '@ant ...

  4. mongodb关机重启

    正确关闭 mongodb 查看 mongodb 进程 ps -ef | grep mongodb # 或者 ps -aux | grep mongodb 杀掉 mongodb 进程(不推荐) kill ...

  5. 如何在 PostgreSQL 中运行 TLS 回归测试

    概述 本文将分享一个简单的步骤,介绍如何在 PostgreSQL 中运行 SSL/TLS 回归测试. Postgres 回归测试 每当我们想要添加新功能或进行修复时,都应该运行 PostgreSQL ...

  6. 基础指令:mkdir、ls、cd、pwd、touch、rm、mv、cp、echo、cat、关机与重启

    目录 1. 创建目录 2. 查看目录内容 3. 进入指定目录(传送) 4. 显示当前所在位置 5. 创建文件 6. 删除文件或目录 7. 移动文件 8. 复制文件或目录 9. echo输出信息到屏幕 ...

  7. 前后端不分离中使用 Ajax 请求(Django3.2)

    博客地址:https://www.cnblogs.com/zylyehuo/ view.py def get_tokens(request): token = get_token(request) r ...

  8. oracle怎么查询重复的数据

    在oracle中,可以利用count()函数配合select查询语句来查询重复的数据,语法为"select userCode from user group by userCode havi ...

  9. linux下nginx常用命令

    1.查找nginx路径:whereis nginx2.启动 service nginx start3.查看Nginx的版本号:nginx -V4.停止 nginx -s stop5.退出 nginx ...

  10. 【电脑】重装Win10之后无法唤醒和正常关机(Y9000P 2022)

    问题: Y9000P 2022 改Windows10后经常关机关不全(自带键盘灯亮,电源指示灯不灭),这还不是最重要的,它一会儿不用到时间自动休眠后还经常唤醒不了 解决: 两个问题,总结一下: 一.关 ...