P5047 [Ynoi2019模拟赛]Yuno loves sqrt technology II(二次离线莫队 + 权值分块,卡常)

在这里插入图片描述


时限 300ms,非常卡常,开氧气优化反向加速,连 T 5个点。

显然有莫队 + 权值树状数组的做法,复杂度为 n n log n n \sqrt n \log n ,时限再开20倍就有希望卡过去了

二次离线莫队的做法:
设上一次维护的区间是 [ l , r ] [l,r] ,当前询问的区间是 [ L , R ] [L,R] ,当移动右端点 r 时(此时左端点不变),贡献的改变量为 [ l , r 1 ] [l,r - 1] 中 权值 比 a [ r ] a[r] 小的数字的个数(普通莫队用树状数组可以在线求 [ l , r 1 ] [l,r - 1] 中权值比 a [ r ] a[r] 小的数字的个数),注意这个贡献可以通过差分得到:用 [ 1 , r 1 ] [ 1 , l 1 ] [1,r - 1] - [1,l - 1] 就得到了这个答案。 而 [ 1 , r 1 ] [1,r - 1] 这部分可以 n log n n\log n 预处理。

对于 [ 1 , l 1 ] [1,l - 1] :将这一次询问的移动存到 下标为 l l 的容器中,然后用类似扫描线的做法:从 1 1 扫到 n n ,分别处理这些询问,边维护边处理。

莫队大约有 n m n\sqrt m 次移动,二次离线后就有 n log m n \log m 次询问,但只有 n n 次维护,需要一个 O ( 1 ) O(1) 查询, O ( n ) O(\sqrt n) 更新的数据结构:权值分块

其次, n m n\sqrt m 个操作全部存入 空间复杂度也达到 n m n \sqrt m ,会爆空间:
当 右端点 r 移动时,左端点 l 并没有移动,可以直接将移动的区间 [ r , R ] [r,R] 存入到 左端点容器中,处理时遍历这个区间即可,空间复杂度降为 O ( m ) O(m)

关于左端点的处理:和右端点的处理方法类似,先移动右端点之后,右端点固定 再移动左端点,讨论方法相同。

由于二次离线 ans[i] 存的是上一次操作和这一次操作的贡献改变量,要得到真正的答案需要将 ans[i] 按照莫队处理的顺序求前缀和

对于权值分块:原先写的是 维护块内前缀和 和 块间前缀和,但这样维护常数相对大一点 更优的做法是直接用分块维护前缀和数组(后缀和同理),对于每一次单点更新,需要修改区间 [ p , n ] [p,n] 的数值,对整块打标记,零散块暴力修改即可,复杂度为 O ( n ) O(\sqrt n) ,但代码更短更好些。


所谓的二次离线,第一次离线以 n m n \sqrt m 的复杂度处理所有区间询问,在莫队处理询问的过程中变成存储所有操作,第二次离线则是扫描线 从1 到 n 边扫描边处理

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
#define pii pair<int,int>
#define fir first
#define sec second
typedef long long ll;
#define lowbit(i) (i & (-i))
int n,m,a[maxn],block,t[maxn],tot,tp,mx[maxn],mi[maxn],sum[maxn];
inline int read() {
    char cc = getchar(); int cn = 0, flus = 1;
    while(cc < '0' || cc > '9') {  if( cc == '-' ) flus = -flus;  cc = getchar();  }
    while(cc >= '0' && cc <= '9')  cn = cn * 10 + cc - '0', cc = getchar();
    return cn * flus;
}
inline void upd(int x,int v) {
	for (int i = x; i <= n; i += lowbit(i))
		sum[i] += v;
}
inline int qry(int x) {
	int tot = 0;
	for (int i = x; i; i -= lowbit(i))
		tot += sum[i];
	return tot;
}
struct node {
	int id,l,r;
	node(int id = 0,int l = 0,int r = 0) {
		this -> id = id;
		this -> l = l;
		this -> r = r;
	}
	bool operator < (const node &rhs) const {
		return (l / block + (l % block > 0)) == (rhs.l / block + (rhs.l % block > 0)) ? r < rhs.r : l < rhs.l;
	}
}q[maxn];
struct ss{
	int l,r,id,v;				// t 代表是取较大值还是较小值, v 代表加还是减 
	ss(int l = 0,int r = 0,int id = 0,int v = 0) {
		this -> l = l;
		this -> r = r;
		this -> id = id;
		this -> v = v;
	}
};
vector<ss> g[maxn],h[maxn];
struct Block {							//值域分块 
	int siz,num,a[maxn],p[maxn],sum[1000];
	void init() {
		siz = sqrt(n);
		num = n / siz + (n % siz > 0);
		memset(p,0,sizeof p);
		memset(sum,0,sizeof sum);
	}
	void upd_pre(int x,int v) {
		int pos = x / siz + (x % siz > 0);
		for (int i = x; i <= min(pos * siz,n); i++)
			p[i] += v;
		for (int i = pos + 1; i <= num; i++)
			sum[i] += v;
	}
	void upd_suf(int x,int v) {
		int pos = x / siz + (x % siz > 0);
		for (int i = (pos - 1) * siz + 1; i <= x; i++)
			p[i] += v;
		for (int i = 1; i < pos; i++)
			sum[i] += v;
	}
	int ask(int x) {
		return p[x] + sum[x / siz + (x % siz > 0)];
	}
	int qry(int l,int r) {
		if(l > r) return 0;
		int lp = l / siz + (l % siz > 0);
		int rp = r / siz + (r % siz > 0);
		int ans = 0;
		if(lp < rp) ans += sum[rp - 1] - sum[lp];
		if(lp == rp) {
			ans += p[r] - (l == (lp - 1) * siz + 1 ? 0 : p[l - 1]);
		} else {
			ans += p[lp * siz] - (l == (lp - 1) * siz + 1 ? 0 : p[l - 1]);
			ans += p[r];
		}
		return ans;
	}
}B;
int curleft,curright;
ll ans[maxn];
void modify(int l,int r,int x) {						//莫队进行二次离线 
	if (curright < r) {
		g[curleft].push_back(ss(curright + 1,r,x,1));
	} else {
		g[curleft].push_back(ss(r + 1,curright,x,-1));
	}
	curright = r;
	if (l < curleft) {
		h[curright].push_back(ss(l,curleft - 1,x,1));	
	} else {
		h[curright].push_back(ss(curleft,l - 1,x,-1));
	}
	curleft = l;
}
void solve() {										//扫描
	B.init();
	for (int i = 1; i <= n; i++) {
		for (auto it : g[i])
			for (int k = it.l; k <= it.r; k++)
				ans[it.id] += 1ll * (mx[k] - B.ask(a[k] + 1)) * it.v;	
		B.upd_suf(a[i],1);
	}
	B.init();
	for (int i = 1; i <= n; i++) {
		B.upd_pre(a[i],1);
		for (auto it : h[i]) 
			for (int k = it.l; k <= it.r; k++)
				ans[it.id] += 1ll * (B.ask(a[k] - 1) - mi[k]) * it.v;
	}
	for (int i = 1; i <= m; i++)
		ans[q[i].id] += ans[q[i - 1].id];
}
int main() {
	n = read(); m = read();
	block = sqrt(n);
	for (int i = 1; i <= n; i++) {
		a[i] = read();
		t[i] = a[i];
	}
	sort(t + 1,t + n + 1);
	tp = unique(t + 1,t + n + 1) - t - 1;
	for (int i = 1; i <= n; i++)
		a[i] = lower_bound(t + 1,t + tp + 1,a[i]) - t;
	for (int i = 1; i <= n; i++) {
		mx[i] = i - qry(a[i]) - 1;
		mi[i] = qry(a[i] - 1);
		upd(a[i],1);
	}
	for (int i = 1,x,y; i <= m; i++) {
		x = read(); y = read();
		q[i] = node(i,x,y);
	}
	sort(q + 1,q + m + 1);
	curleft = 1; curright = 0;
	for (int i = 1; i <= m; i++)
		modify(q[i].l,q[i].r,q[i].id);
	solve();
	for (int i = 1; i <= m; i++)
		printf("%lld\n",ans[i]);
	return 0;
}
/*
16 6
1 3 5 2 4 7 6 8 10 9 12 11 13 16 15 14
1 2
2 9
3 7
8 16
7 14
1 16
*/
发布了332 篇原创文章 · 获赞 10 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_41997978/article/details/104123738
今日推荐