P1198 [JSOI2008]最大数
多姿多彩的做法
暴力(非正解)
暴力还是很好打的。整体时间复杂度为 (极端情况, 为数列长度)。
线段树
考试的时候本来想这么写的,可是写挂了,我太弱了。
由于初始集合是 ,我们不需要写性能瓶颈(时间复杂度 )的建树,只需要写维护最大值(记得取模)的单点修改与区间查询就成。
整体时间复杂度为 。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int t[800005];
int m,d;
char op;
int x,tt,cnt;
void pushup(int i){
t[i]=max(t[i<<1],t[i<<1|1])%d;
}
void update(int pv,int v,int i,int l,int r){
if(l==r){
t[i]=v;
return;
}
int mid=(l+r)>>1;
if(mid>=pv){
update(pv,v,i<<1,l,mid);
}
if(mid<pv){
update(pv,v,i<<1|1,mid+1,r);
}
pushup(i);
}
int ask(int ql,int qr,int i,int l,int r){
if(ql<=l&&qr>=r){
return t[i];
}
int mid=(l+r)>>1;
int result=LLONG_MIN;
if(ql<=mid){
result=max(result,ask(ql,qr,i<<1,l,mid));
}
if(qr>mid){
result=max(result,ask(ql,qr,i<<1|1,mid+1,r));
}
return result;
}
signed main(){
cin>>m>>d;
for(int i=1;i<=m;i++){
cin>>op>>x;
if(op=='A'){
update(cnt+1,(x+tt)%d,1,1,m);
cnt++;
}
else{
if(x==0)tt=0;
else tt=ask(cnt-x+1,cnt,1,1,m)%d;
cout<<tt<<endl;
}
}
return 0;
}
单调栈+单调栈上二分
由于只查询最后几个元素,因此只要建两个单调栈,一个维护值,一个维护位置。然后二分找下标。
整体时间复杂度为 。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int m,t,d;
int sta[3][200005],top,cnt;
void push(int k){
cnt++;
while(k>sta[1][top]&&top>0){
top--; // Pop if it's smaller than the element it will be push
}
sta[0][++top]=cnt;
sta[1][top]=k;
}
bool check(int x,int v){
return sta[0][x]<v;
}
int binarySearch(int v,int l,int r){ // Binary Search(Lower Bound)
while(l<r){
int mid=(l+r)>>1;
if(check(mid,v)){
l=mid+1;
}
else{
r=mid;
}
}
t=sta[1][r];
return t;
}
signed main(){
cin>>m>>d;
for(int i=1;i<=m;i++){
char op;int x;
cin>>op>>x;
if(op=='A'){
push((t+x)%d);
}
else{
cout<<binarySearch(cnt-x+1,1,top)<<endl;
}
}
return 0;
}
ST表
参考自这里
这里的ST表 维护的是区间 的最值。
ST表可以维护RMQ问题,本问题可以用ST表解答,插入不会影响原来的ST表。
时间复杂度还是熟悉的 。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int a[200005],f[200005][25],t,d,n,m;
void change(int u){
f[u][0]=a[u];
for(int i=1;u-(1<<i)>=0;i++){
f[u][i]=max(f[u][i-1],f[u-(1<<(i-1))][i-1]);
}
}
int find(int x,int y){
double t=log(y-x+1)/log(2);
int K=t;
return max(f[y][K],f[x+(1<<K)-1][K]);
}
signed main(){
cin>>m>>d;
for(int i=1;i<=m;i++){
char c;int x;
cin>>c>>x;
if(c=='A'){
a[++n]=(x+t)%d; // it's simliar than BF algorithm
change(n);
}
else{
int ans;
if(ans==1){
cout<<a[n]<<endl;
t=a[n];
}
else{
ans=find(n-x+1,n);
cout<<ans<<endl;
t=ans;
}
}
}
return 0;
}
单调队列+单调队上二分
考虑维护一个单调队列,维护以最后一个数结束的极大值。
插入数字=入队
接着二分查询执行查询操作。
整体时间复杂度为 。
这里写的是std::lower_bound
(不想粘贴二分模板)。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int a[200005],q[200005];
int m,d,head,tail,t;
signed main(){
cin>>m>>d;
for(int i=1;i<=m;i++){
char op;int x;
cin>>op>>x;
if(op=='A'){
a[++head]=(x+t)%d;
while(tail>0&&a[q[tail-1]]<a[head]){
tail--;
}
q[tail++]=head;
}
else{
cout<<(t=a[*lower_bound(q,q+tail,head-x+1)])<<endl;
}
}
return 0;
}
单调队列+并查集维护下标
其实只要维护一个集合(可以用并查集实现),存储下标,找根即可。
出队时合并集合。
使用路径压缩优化并查集,整体时间复杂度 ,一般为 。可以说是线性的。
注意:不能按秩合并,会全WA的!
#include <bits/stdc++.h>
//#define int long long
using namespace std;
int a[200005],q[200005];
int m,d,head,tail,t;
int fa[200005];
//int Rank[200005];
int find(int x){
if(fa[x]==x)return x;
else return fa[x]=find(fa[x]);
}
int merge(int x,int y){
int a=find(x),b=find(y);
fa[a]=b;
}
signed main(){
srand(time(0));
cin>>m>>d;
for(int i=1;i<=m;i++){
char op;int x;
cin>>op>>x;
if(op=='A'){
a[++head]=(x+t)%d;
fa[head]=head;
//Rank[head]=rand();
while(tail>0&&a[q[tail-1]]<a[head]){
merge(q[tail-1],head);
tail--;
}
q[tail++]=head;
}
else{
cout<<(t=a[find(head-x+1)])<<endl;
}
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人