LG5386 Cnoi2019 数字游戏 题解

Cnoi2019 数字游戏

题意

给定一个长度为 \(n\) 的排列,你需要回答 \(m\) 个询问 \((l,r,x,y)\),表示询问 \([l,r]\) 这个区间内有多少个子区间的值域是 \([x,y]\) 的子集。

Sub1:\(n,m\le 3\times 10^4\)

样例:1 2 5 4 3 做询问 1 5 1 4 的答案是 6

如下区间会被统计进答案:

\( [1,1]\\ [1,2]\\ [2,2]\\ [4,4]\\ [4,5]\\ [5,5]\\ \)

对了,这题时限 \(7\) 秒。

做法

下面认为 \(n,m\) 同阶,一律以 \(n\) 表示。

  • \(\mathcal{O}(n^3)\)

每次暴力枚举,st表查询区间最值即可。

  • \(\mathcal{O}(n^2)\)

考虑到我们每一次只需要找到满足条件的极长区间即可。

(对于极长的定义:没有更长的就认为是极长,比如样例,极长区间是 \([1,2]\)\([4,5]\)。)

这个东西是好找的,暴力遍历找然后记录下长度就可以直接算答案了。

据说有小常数选手就这么过了?

  • \(\mathcal{O}(n\sqrt{n}\log n)\)

我们对于 \(x,y\) 两维做莫队,新建一个数组 \(b_i\) 表示原排列里的数 \(a_i\) 是否在当前莫队限制的值域内。

此时我们的问题就是找到 \(b_i\) 里面在 \([l,r]\) 内的所有极长 \(1\) 连续段(下称“极长段”)并统计答案,线段树能够完成这个任务,而且其实出乎意料地好写。

那么这道题就得到了解决。

进一步

Sub2:\(n,m\le 2\times 10^5\)


做法

  • \(\mathcal{O}(n\sqrt{n}\log\sqrt{n})\)

这个做法我是从同级大佬那里搞到的,我也没想到能过。

我们分块,对每个块维护线段树就能够降低修改的时间复杂度。

每次询问暴力扫所有块,由于询问只有 \(m\) 次,不会影响总的时间复杂度。

然后你会发现这个东西实际上只是砍掉了一个 \(2\) 的常数……

这个东西由于原题的时限有整整 \(7\) 秒,常数写小点可以 \(6\) 秒多点稳过。

下面这份代码需要看点脸,评测机波动一下就能过去。

如果想要更稳妥的写法,建议zkw线段树。

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=200010,B=500;
struct node
{int llen,rlen;ll sum;int all_one;//线段树的维护自行推导,不难。node operator+(node oth)const noexcept{node ret={llen,oth.rlen,sum+oth.sum+rlen*oth.llen,all_one&oth.all_one};ret.llen+=all_one*oth.llen;ret.rlen+=oth.all_one*rlen;return ret;}
};
class Segment_Tree
{
public:node sgtr[1000000/B];void Change(int k,int l,int r,int x,int c){if(l==x&&x==r){if(c)sgtr[k]={1,1,1,1};else sgtr[k]={0,0,0,0};return;}int mid=(l+r)>>1;if(x>mid)Change(k*2+1,mid+1,r,x,c);else Change(k*2,l,mid,x,c);sgtr[k]=sgtr[k*2]+sgtr[k*2+1];return;}node Query(int k,int l,int r,int x,int y){if(x<=l&&r<=y)return sgtr[k];int mid=(l+r)>>1;if(y<=mid)return Query(k*2,l,mid,x,y);if(x>mid)return Query(k*2+1,mid+1,r,x,y);return Query(k*2,l,mid,x,mid)+Query(k*2+1,mid+1,r,mid+1,y);}
}sgtr[B];
int a[N],r[N];
int n,q;
int block_len,block_num;
int bl[N],st[N],ed[N],id[N];
inline void Build_Block()
{//分块。block_len=sqrt(n);block_num=(n-1)/block_len+1;for(int i=1;i<=n;i++)bl[i]=(i-1)/block_len+1;for(int i=1;i<=block_num;i++)st[i]=(i-1)*block_len+1,ed[i]=i*block_len;for(int i=1;i<=n;i++)id[i]=i-st[bl[i]]+1;ed[block_num]=n;return;
}
struct query
{int id,l,r,x,y;int operator<(query oth){if(bl[x]==bl[oth.x])return (y<oth.y)^(bl[x]&1);return x<oth.x;}
}Q[N];
ll final_ans[N];
int X,Y;
inline void add(int x)
{sgtr[bl[x]].Change(1,1,ed[bl[x]]-st[bl[x]]+1,id[x],1);return;
}
inline void del(int x)
{sgtr[bl[x]].Change(1,1,ed[bl[x]]-st[bl[x]]+1,id[x],0);return;
}
inline ll Query(int l,int r)
{//询问区间 $[l,r]$ 在当前 $[X,Y]$ 限制下的答案。if(bl[l]==bl[r])return sgtr[bl[l]].Query(1,1,id[ed[bl[l]]],id[l],id[r]).sum;node ret=sgtr[bl[l]].Query(1,1,id[ed[bl[l]]],id[l],id[ed[bl[l]]]);for(int i=bl[l]+1;i<bl[r];i++)ret=ret+sgtr[i].Query(1,1,id[ed[bl[i]]],1,id[ed[bl[i]]]);ret=ret+sgtr[bl[r]].Query(1,1,id[ed[bl[r]]],1,id[r]);return ret.sum;
}
int main()
{scanf("%d%d",&n,&q);for(int i=1;i<=n;i++)scanf("%d",a+i),r[a[i]]=i;for(int i=1;i<=q;i++)scanf("%d%d%d%d",&Q[i].l,&Q[i].r,&Q[i].x,&Q[i].y),Q[i].id=i;Build_Block();sort(Q+1,Q+q+1);int lst=0;X=1,Y=0;for(int i=1;i<=q;i++){int l=Q[i].l,r=Q[i].r,x=Q[i].x,y=Q[i].y;while(X>x)X--,add(::r[X]);while(Y<y)Y++,add(::r[Y]);while(X<x)del(::r[X]),X++;while(Y>y)del(::r[Y]),Y--;final_ans[Q[i].id]=Query(l,r);}for(int i=1;i<=q;i++)printf("%lld\n",final_ans[i]);return 0;
}
  • \(\mathcal{O}(n\sqrt{n})\)

实际上上面那个卡常很不优美。

我们考虑莫队的时候实际上有 \(\mathcal{O}(n\sqrt{n})\) 次修改和 \(\mathcal{O}(n)\) 次询问。

因此如果我们找到一个 \(\mathcal{O}(1)\) 修改,\(\mathcal{O}(\sqrt{n})\) 查询的数据结构,就可以以更优的时间复杂度解决。

正好,我们还真能找到。

做序列分块,每个块内维护答案即可。

维护你可以考虑很多种实现,有链表、并查集等等。

这里我是从同级大佬那里学来的做法,对于每个极长段,在其左端点和右端点记录一下,这样就可以写出下面代码里的 insertundo 了。

然后这样维护有缺陷,就是删去一个数没法搞。

实际很好解决,外层的莫队改为回滚莫队即可。

那么这道题就解决了,据说还有双倍经验。

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=200010;
int a[N],r[N];
int n,q;
int block_len,block_num;
int bl[N],st[N],ed[N];
inline void Build_Block()
{block_len=sqrt(n);block_num=(n-1)/block_len+1;for(int i=1;i<=n;i++)bl[i]=(i-1)/block_len+1;for(int i=1;i<=block_num;i++)st[i]=(i-1)*block_len+1,ed[i]=i*block_len;ed[block_num]=n;return;
}
//本题最恶心的也就是维护极长段了。
//维护方法不止一种,但都很恶心。
//一种实现:在每个极长段的最左/右边维护该段长度。
ll llen[N],rlen[N];
#define mp make_pair
#define fr first
#define sc second
stack<pair<int,pair<int,int>>>stk;
//使用栈进行操作撤销,用于回滚
ll ans[N];//每个块的答案
inline void insert(int x)
{//插入新点int ls=0,rs=0,id=bl[x];if(llen[x+1]&&bl[x+1]==bl[x]&&rlen[x-1]&&bl[x-1]==bl[x]){//左右同时相连int lp=x-1,rp=x+1;ans[id]-=(rlen[lp]*(rlen[lp]+1)/2)+(llen[rp]*(llen[rp]+1)/2);ls=lp-rlen[lp]+1,rs=rp+llen[rp]-1;llen[ls]+=llen[rp]+1;rlen[rs]+=rlen[lp]+1;llen[rp]=rlen[lp]=0;ans[id]+=(llen[ls]*(rlen[rs]+1)/2);}else if(llen[x+1]&&bl[x+1]==bl[x]){//右端相连int rp=x+1;ans[id]+=llen[rp]+1;rs=rp+llen[rp]-1;llen[x]=llen[rp]+1;rlen[rs]++;llen[rp]=0;}else if(rlen[x-1]&&bl[x-1]==bl[x]){//左端相连int lp=x-1;ans[id]+=rlen[lp]+1;ls=lp-rlen[lp]+1;rlen[x]=rlen[lp]+1;llen[ls]++;rlen[lp]=0;}else llen[x]=rlen[x]=1,ans[id]++;//完全不相连stk.push(mp(x,mp(ls,rs)));return;
}
inline void undo()
{//撤销操作int x=stk.top().fr,ls=stk.top().sc.fr,rs=stk.top().sc.sc;int id=bl[x];stk.pop();if(ls&&rs){//从中间断开ans[id]-=llen[ls]*(rlen[rs]+1)/2;llen[ls]=x-ls;rlen[x-1]=x-ls;llen[x+1]=rs-x;rlen[rs]=rs-x;ans[id]+=(llen[ls]*(llen[ls]+1)/2)+(rlen[rs]*(rlen[rs]+1)/2);}else if(ls){//去掉最右端ans[id]-=llen[ls];llen[ls]--,rlen[x-1]=rlen[x]-1,rlen[x]=0;}else if(rs){//去掉最左端ans[id]-=rlen[rs];rlen[rs]--,llen[x+1]=llen[x]-1,llen[x]=0;}else llen[x]=rlen[x]=0,ans[id]--;//去掉独块return;
}
int X,Y;
inline ll query(int l,int r)
{ll ret=0,len=0;if(bl[r]-bl[l]<=1){for(int i=l;i<=r;i++){if(X<=a[i]&&a[i]<=Y)len++;else ret+=len*(len+1)/2,len=0;}return ret+len*(len+1)/2;}for(int i=l;i<=ed[bl[l]];i++)if(X<=a[i]&&a[i]<=Y)len++;else ret+=len*(len+1)/2,len=0;for(int i=bl[l]+1;i<bl[r];i++){if(llen[st[i]]==block_len&&rlen[ed[i]]==block_len)len+=llen[st[i]];//整块都是,直接累加else{len+=llen[st[i]];ret+=len*(len+1)/2;ret+=ans[i]-llen[st[i]]*(llen[st[i]]+1)/2-rlen[ed[i]]*(rlen[ed[i]]+1)/2;//把开头和结尾要拼起来的部分去掉len=rlen[ed[i]];}}for(int i=st[bl[r]];i<=r;i++)if(X<=a[i]&&a[i]<=Y)len++;else ret+=len*(len+1)/2,len=0;return ret+len*(len+1)/2;
}
inline void clear()
{memset(llen,0,sizeof llen);memset(rlen,0,sizeof rlen);memset(ans,0,sizeof ans);while(!stk.empty())stk.pop();return;
}
struct query
{int id,l,r,x,y;bool operator<(query oth){if(bl[x]==bl[oth.x])return y<oth.y;return x<oth.x;}
}Q[N];
ll final_ans[N];
int main()
{scanf("%d%d",&n,&q);for(int i=1;i<=n;i++)scanf("%d",a+i),r[a[i]]=i;for(int i=1;i<=q;i++)scanf("%d%d%d%d",&Q[i].l,&Q[i].r,&Q[i].x,&Q[i].y),Q[i].id=i;Build_Block();sort(Q+1,Q+q+1);int lst=0;for(int i=1;i<=q;i++){int l=Q[i].l,r=Q[i].r,x=Q[i].x,y=Q[i].y;if(bl[x]!=lst){clear();lst=bl[x];X=ed[lst]+1;Y=ed[lst];}if(bl[x]==bl[y]){for(int i=x;i<=y;i++)insert(::r[i]);swap(x,X),swap(y,Y),final_ans[Q[i].id]=query(l,r),swap(x,X),swap(y,Y);for(int i=x;i<=y;i++)undo();continue;}while(Y<y)Y++,insert(::r[Y]);while(X>x)X--,insert(::r[X]);final_ans[Q[i].id]=query(l,r);while(X<ed[lst]+1)undo(),X++;}for(int i=1;i<=q;i++)printf("%lld\n",final_ans[i]);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/863722.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P11503 [NordicOI 2018] Nordic Camping

P11503 [NordicOI 2018] Nordic Camping 花了我挺长时间。 帐篷都是正方形的,可以枚举左上角,二分正方形边长,二维前缀和判断是否合法。这部分复杂度为 \(O(n^2\log n)\)。处理出来后,问题似乎就变成了矩形取最大值,单点查询。直接做是 \(\log^2\) 的,65 pts。具体就是,…

OpenXR间接链接、API层总体排序、加载器设计

OpenXR间接链接 通过加载器间接链接,应用程序动态生成OpenXR命令调度表。如果找不到加载器,或者只有比应用程序更旧的API,则此方法允许应用程序正常失败。为此,应用程序在加载器库上,使用特定平台的动态符号,查找(如dlsym())xrGetInstanceProcAddr命令的地址。一旦发…

OpenXR™加载器-设计、操作、调用链

OpenXR™加载器-设计和操作 1.2.1概述 OpenXR是一个分层体系结构,由以下元素组成: 1)OpenXR应用程序 2)OpenXR加载程序 3)OpenXR API层 4)OpenXR运行时间 一般概念适用于Windows和Linux的系统的加载程序。 首先,让把OpenXR环境看作一个整体。OpenXR应用程序位于执行链的…

经典专著《AI芯片开发核心技术详解》、《智能汽车传感器:原理设计应用》、《TVM编译器原理与实践》、《LLVM编译器原理与实践》4本书推荐

4本书推荐《AI芯片开发核心技术详解》、《智能汽车传感器:原理设计应用》、《TVM编译器原理与实践》、《LLVM编译器原理与实践》由清华大学出版社资深编辑赵佳霓老师策划编辑的新书《AI芯片开发核心技术详解》已经出版,京东、淘宝天猫、当当等网上,相应陆陆续续可以购买。该…

使用format_obproxy_digest_log工具分析obproxy网络层耗时SQL

之前写过一个博客,介绍 ob_tools包 来实施抓取 observer 层的 gv$ob_sql_audit 的SQL,还提供一些分析SQL来通过不同维度分析缓慢的业务SQL语句,免得和应用扯皮说数据库执行SQL慢。 但是分析出服务端业务SQL语句执行时间还不够,应用也有可能会和你扯皮说obproxy转发慢,也不…

win 安装Android子系统WSA

win 安装Android子系统WSA 仅适用于Windows10和11。 在WSA中,对于要运行的应用不能保证较好的Android兼容性。 1.启用虚拟机平台功能 启用虚拟机平台: PS C:\Users\xxx> dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart然后重启计…

Windows环境配置Nginx服务实现负载均衡

系统环境:win10 测试服务:.net6.0+webapi 一、本地创建一个webapi项目二、新建一个api控制器,里面编写一个测试方法三、我直接把这一个项目复制了3份,然后修改控制器方法中的返回值分别为value01,value02,value03四、分别启动三个程序,并且配置不同的端口号Postman/Apif…

二维数组的使用

1.二位数组的理解 Java 语言里提供了支持多维数组的语法 如果说可以把一维数组当成几何中的线性图形,那么二维数组就相当于是一个表格,像右图Excel中的表格一样。 对于二维数组的理解,我们可以看成是一维数组array1又作为另一个一维数组array2的元素而存在。其实,从数组底层…

https申请+部署

1.申请参考:https://developer.aliyun.com/article/1635083 自动就行。 2.成功后会下载到两个文件。一个.poe 一个.key 部署 1.在/etc/nginx/conf.d里面新建一个文件夹cert,然后把这两个文件放进去。2.返回到nginx.conf里面配置, 这是原来的 server{listen 80;serve…

使用 SK Plugin 给 LLM 添加能力

前几篇我们介绍了如何使用 SK + ollama 跟 LLM 进行基本的对话。如果只是对话的话其实不用什么 SK 也是可以的。今天让我们给 LLM 整点活,让它真的给我们干点啥。 What is Plugin?Plugins are a key component of Semantic Kernel. If you have already used plugins from Ch…

FreeNAS 11.2-U6 简易上手指南

NAS是一个网络附属存储管理器,简单的可以理解为一个可以将数据存储至网络设备的服务。 NAS一般都会支持数据冗余、日志、压缩以及校验等功能。 国内最出名的应该就是群晖的系统了,当然我们这里主要讲解的是FreeNAS。 FreeNAS是一个基于FreeBSD进行二次开发的开源NAS系统,其支…