P4396 [AHOI2013] 作业

原题链接

前置知识:莫队+值域分块

题意:给你长为 n n n 的序列,多次询问,每次询问序列 [ l , r ] [l,r] [l,r] 中数值在 [ a , b ] [a,b] [a,b] 中的数的个数及数值个数。

区间询问,不带修改,支持离线,直接考虑莫队。出题人很良心的把值域控制在 [ 1 , 1 e 5 ] [1,1e5] [1,1e5],启示我们可以开个权值线段树或值域树状数组,来维护数值。然而这样的时间复杂度是 O ( n n l o g n ) O(n\sqrt n_{}logn) O(nn logn) 的,对于此题的数据范围很难跑过。

再想莫队算法是如何实现的,发现在跑莫队的过程中,实际最多只有 n m n\sqrt m nm 次修改和 m m m 次询问,我们想要平衡这两个过程,就可以应用值域分块。按值域分块,单点加减是可以 O ( 1 ) O(1) O(1) 做到的,并且实现很容易。对于查询,就 O ( n ) O(\sqrt n) O(n ) 的常规查询即可。

n n n m m m 同阶,故经过值域分块的平衡后,此题的时间复杂度就被我们优化到 O ( n n ) O(n\sqrt n) O(nn )

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
	return x*f;
}
int n,m,w[N],L[N],R[N],belong[N],cnt,sz[N],num[N],vis[N],pos[N];
struct node{
	int l,r,a,b,id;
}q[N];
struct Node{
	int sum1,sum2;
}ans[N];
bool cmp(node a,node b){
	if(pos[a.l]==pos[b.l]) return a.r<b.r;
	return a.l<b.l;
}
void add(int x){
	int t=belong[x];
	if(!vis[x]) num[t]++;
	vis[x]++,sz[t]++;
}
void del(int x){
	int t=belong[x];
	if(vis[x]==1) num[t]--;
	vis[x]--,sz[t]--;
}
Node query(int l,int r){
	int p=belong[l],q=belong[r];
	if(p==q){
		int sum1=0,sum2=0;
		for(int i=l;i<=r;i++) if(vis[i]) sum2++,sum1+=vis[i];
		return Node{sum1,sum2};
	}
	int sum1=0,sum2=0;
	for(int i=l;i<=R[p];i++) if(vis[i]) sum2++,sum1+=vis[i];
	for(int i=L[q];i<=r;i++) if(vis[i]) sum2++,sum1+=vis[i];
	for(int i=p+1;i<=q-1;i++) sum1+=sz[i],sum2+=num[i];
	return Node{sum1,sum2};
}
void init(){
	int nn=1e5,t=sqrt(nn),cnt=nn/t;if(nn%cnt) cnt++;
	for(int i=1;i<=cnt;i++) L[i]=(i-1)*t+1,R[i]=i*t;
	R[cnt]=nn;
	for(int i=1;i<=nn;i++) for(int j=L[i];j<=R[i];j++) belong[j]=i;
}
int main(){
	init();
	n=read(),m=read();
	for(int i=1;i<=n;i++) w[i]=read();
	for(int i=1;i<=m;i++) q[i].l=read(),q[i].r=read(),q[i].a=read(),q[i].b=read(),q[i].id=i;
	int t=sqrt(n);for(int i=1;i<=n;i++) pos[i]=(i-1)/t+1;
	sort(q+1,q+1+m,cmp);
	for(int i=1,l=0,r=0;i<=m;i++){
		while(l<q[i].l) del(w[l++]);
		while(r>q[i].r) del(w[r--]);
		while(l>q[i].l) add(w[--l]);
		while(r<q[i].r) add(w[++r]);
		ans[q[i].id]=query(q[i].a,q[i].b);
	}
	for(int i=1;i<=m;i++) cout<<ans[i].sum1<<" "<<ans[i].sum2<<endl;
	return 0;
}
### AHOI2008 计算器问题解析 对于AHOI2008中的计算器问题,题目描述涉及一种特殊的计算器操作模式。该计算器支持两种基本运算:加法和乘法,并且可以执行逆向操作来撤销最近的一次计算。 #### 题目背景与目标 给定一系列的操作指令序列,每条指令可能是增加某个数值、将当前值翻倍或是回退至上一步的结果。程序需模拟这些命令的效果并最终输出指定时刻的状态值[^1]。 #### 数据结构的选择 为了高效处理上述类型的查询请求,在此场景下推荐采用栈(Stack)作为主要的数据存储机制。通过维护一个用于记录历史状态变化的栈表,可以在O(1)时间内完成入栈(push)/出栈(pop),从而满足快速响应的要求。 #### 关键算法逻辑 当遇到`ADD x`这样的正向修改时,只需简单地把新加入的数压入栈顶;而面对`MULTIPLY BY TWO`的情况,则应先保存现有总和再将其加倍后存入堆栈顶部。特别注意的是,“取消”动作意味着弹出最新一次变更前的状态恢复原状即可。 ```cpp #include <iostream> #include <stack> using namespace std; int main() { int n; cin >> n; long long current_value = 0LL; stack<long long> history; while (n--) { string command; cin >> command; if (command == "ADD") { int value_to_add; cin >> value_to_add; // Save the state before addition. history.push(current_value); current_value += value_to_add; } else if (command == "MULTIPLY_BY_TWO") { // Record pre-multiplication status and double it. history.push(current_value); current_value *= 2LL; } else { // UNDO operation if (!history.empty()) { current_value = history.top(); history.pop(); } } } cout << current_value << endl; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值