[Ynoi2015] 我回来了 题解

\(NOIP\) 考前祈福。

实际上,每种伤害 \(d\) 打出的亵渎次数可以转化为:

\[1+\max\limits_{i=0}^{\lceil\frac{n}{d}\rceil}(i[\sum\limits_{j=1}^{i}[sum(jd-d+1,jd)>0]=i]) \]

其中 \(sum(i,j)\) 表示血量为 \([i,j]\) 这个区间的随从数量。

容易想到记录每种伤害 \(d\) 最早亵渎 \(x+1\) 次时的位置 \(g_{d,x}\)。设 \(a_i\) 表示第一个血量为 \(i\) 的随从出现的时间,则有:

\[g_{d,x}=\max\limits_{i=1}^x(\min\limits_{j=id-i+1}^{id}a_j) \]

括号内的部分直接用 \(ST\) 表简单维护,于是轻松求解。

由于总的区间段数为 \(\sum\limits_{i=1}^n\lceil\frac{n}{i}\rceil\),与 \(n\log n\) 同级,所以枚举区间段数的时间复杂度为 \(O(n\log n)\)

考虑一句废话:当亵渎次数增加 \(1\) 时,包含该伤害的询问答案也加 \(1\)

于是建立树状数组,统计现在每种伤害能打出的亵渎次数,询问时区间求值即可。

离线,时间复杂度 \(O(n\log^2n)\)

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
const int M=1e6+5;
int n,m,c[M],opt[M],a[N];
int lq[M],rq[M],st[N][20];
vector<int>ad[M];
void add(int x,int y){
	for(;x<=m;x+=x&-x) c[x]+=y;
}int sum(int x){
	int re=0;
	for(;x;x-=x&-x) re+=c[x];
	return re; 
}void get_ST(){
	for(int i=1;i<=n;i++) st[i][0]=a[i];
	for(int i=0;i<19;i++)
		for(int j=1;j+(1<<(i+1))-1<=n;j++)
			st[j][i+1]=min(st[j][i],st[j+(1<<i)][i]);
}int rmq(int l,int r){
	int k=log2(r-l+1),x=r-(1<<k)+1;
	return min(st[l][k],st[x][k]);
}int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++) a[i]=m+1;
	for(int i=1;i<=m;i++){
		cin>>opt[i];
		if(opt[i]==1){
			int h;cin>>h;
			if(a[h]>m) a[h]=i;
		}else cin>>lq[i]>>rq[i];
	}get_ST();
	for(int i=1;i<=n;i++)
		for(int j=1,t=0;j<=n;j+=i)
			t=max(t,rmq(j,min(j+i-1,n))),ad[t].push_back(i);
	for(int i=1;i<=n;i++) add(i,1);
	for(int i=1;i<=m;i++){
		for(auto x:ad[i]) add(x,1);
		if(opt[i]==1) continue;
		cout<<sum(rq[i])-sum(lq[i]-1)<<"\n";
	}return 0;
}
posted @ 2024-11-29 17:45  长安一片月_22  阅读(3)  评论(0编辑  收藏  举报