CF1824D

原题

翻译

我们定义\(f(l,r,x)=\sum_{j=1}^{x}{\sum_{i=l}^{\min{(j,r)}}{g(i,j)}}\),容易发现对于一个询问的答案\(ans = f(l,r,y)-f(l,r,x-1)\)

我们扫描先维护右端点\(r\)从左向右移动,线段树下标\(i\)上的值维护的是\(g(i,r)\)

我们考虑再维护一个01数组\(b_i\),表示\(a_i\)这个数是否是在\([i,r]\)中第一次出现。每次移动右端点时把当前位置戳成\(1\),把上一个和该元素相同的位置戳成\(0\)

我们发现\(g(i,r)\)就等于\(i\)之后第一个\(b_i=1\)的下标。接下来我们考虑加入\(a_r\)后会对哪些答案产生贡献

在修改之前的局面(图盗自:这里

image

我们发现加入\(a_i\)后图中\(b_x\)\(1 \rightarrow 0\),这会让区间\([y+1,x]\)内的值变成\(z\),然后让\([k+1,i]\)这个位置的值覆盖成\(i\)(如下图)

image

但由于这题很卡常,我们可以发现如果\(a_{i-1} \neq a_i\),则\(k=i-1\);而如果\(a_{i-1} = a_i\),则\(x=i-1,z=i\)。因此我们对于第二种修改只需要修改位置\(i\)的值为\(i\)即可

找到这些位置我们可以用\(set\)维护\(b_i=1\)的位置,并通过在\(set\)中二分和移动迭代器来查找到图中的\(y,x,z\)

而我们要找的答案即为右端点\(r=1 \rightarrow x\)的过程中所有区间\([l,r]\)的和

但这只是一个询问的情况,我们发现如果有\(Q\)个询问这么做显然是\(O(Qn\log n)\)的,是无法通过的

但我们发现我们实际上维护了这\(Q\)次询问的答案,只是我们要考虑如何快速的把他们累加

看一眼线段树3,我们发现这个“历史和”很符合我们的要求

于是我们只需要把所有询问离线,把\(x,y\)拆成\(\{l,r,x-1\}\)\(\{l,r,y\}\),线段树维护以下操作:

  1. 区间赋值

  2. 查询区间历史和

以下说一下怎么维护区间赋值历史区间和线段树,会的可以走了


首先建一棵线段树

朴素的,我们先维护题目要求的\(lazytag\):设对这个区间的历史区间和更新了\(hs\)次,把当前区间覆盖成了\(cov\)。然后考虑怎么写\(pushdown\)函数

我们显然需要现进行\(hs\)标记的下传再下传\(cov\),因为如果我们先下传\(cov\),我们就把原来的历史信息覆盖掉了,就无法知道在修改前\(hs\)对历史区间和的贡献

我们发现这个是不太好维护的,这里引用一个大佬的博客,这里这个队列的形容是很贴切的

我们发现如果当前区间的队列中有\(hs\)\(cov\),这时加入了一个\(hs'\),我们是无法直接累加到\(hs\)中的,因为中间隔着\(cov\)影响到了\(hs\)的贡献

但我们发现如果有了\(cov\)操作,我们之后的\(hs\)标记不就是加上了一个常数吗!于是我们只需要再维护一个当前历史区间和加上的常数\(cst\)即可

这时如果队列中有\(hs\)\(cst\)\(cov\),进入了\(hs'\),我们就可以把\(hs'\)\(cov\)合并,\(cst \leftarrow cst + hs' \times cov\)即可

然后注意标记下传的顺序,\(hs\)必须在\(cov\)的前面

最终复杂度\(O(n\log n + Q\log n)\)


\(p.s.2023/9/19\),看了P8868发现自己对这类题的思路多少有点出入

这道题他左端点的范围是固定的,而比赛这题左端点范围不固定。因此理论上我们应该维护右端点递增的历史区间和,而这个貌似是不可做的

但比赛这题和这题做法类似的原因是:如果一个\(l>r\),则\(l\)的值不会被修改,及即使多算了\(l\)的答案也不会对最终答案产生影响,因为他没有被更新,所以贡献为\(0\)

代码:

#include <bits/stdc++.h>
// #pragma GCC optimize(2)
#define pcn putchar('\n')
#define ll long long
#define MP make_pair
#define fi first
#define se second
#define gsize(x) ((int)(x).size())
#define Min(a, b) (a = min(a, b))
#define Max(a, b) (a = max(a, b))
#define For(i, j, k) for(int i = (j), END##i = (k); i <= END##i; ++ i)
#define For__(i, j, k) for(int i = (j), END##i = (k); i >= END##i; -- i)
#define Fore(i, j, k) for(int i = (j); i; i = (k))
//#define random(l, r) ((ll)(rnd() % (r - l + 1)) + l)
using namespace std;

namespace IO {
	template <typename T> inline T read(T &num){
	    num = 0; T f = 1; char c = ' '; while(c < '0' || c > '9') if((c = getchar()) == '-') f = -1;
	    while(c >= '0' && c <= '9') num = (num << 1) + (num << 3) + (c ^ 48), c = getchar();
	    return num *= f;
	}
	template <typename T> inline void Write(T x){
	    if(x < 0) putchar('-'), x = -x; if(x == 0){putchar('0'); return ;}
	    if(x > 9) Write(x / 10); putchar(x % 10 + '0'); return ;
	}
	inline void putc(string s){ int len = s.size() - 1; For(i, 0, len) putchar(s[ i ]); }
	template <typename T> inline void write(T x, string s = "\0"){ Write( x ), putc( s ); }
}
using namespace IO;

/* ====================================== */

const int maxn = 1e6 + 50;
const ll INF = (1ll << 60);

int n, Q;
int a[ maxn ];
int buc[ maxn ];// 记录上一个和自己数字相同的位置 
set<int> s;// 记录每个数字最后出现的位置 
struct Ask{
	int l, r, x, id, k;
	
	bool operator < (const Ask &y) const{
		return (x ^ y.x ? x < y.x : k < y.k);
	}
} ask[ maxn << 1 ]; 
ll ans[ maxn ];

struct SegmentTree{
	#define ls (p << 1)
	#define rs (p << 1 | 1)
	
	struct Tree{
		ll hs, sum;// 历史区间和;区间和 
		
		Tree operator + (const Tree &y) const{
			Tree res;
			res.hs = hs + y.hs;
			res.sum = sum + y.sum;
			return res;
		}
	} tr[ maxn << 2 ];
	struct Tag{
		ll hs, cst, cov;
		//hs:对当前区间维护历史区间和的次数
		//cst:当前这个区间加上的常数(为了合并hs和cov的临时懒标记)
		//cov:对当前区间的覆盖tag 
	} tag[ maxn << 2 ];
	
	inline void PushUp(int p){
		tr[ p ] = tr[ ls ] + tr[ rs ];
	}
	inline void SmlUpd(int l, int r, Tag k, int p){
		if(k.hs){//如果这个区间维护了k.hs次历史区间和 
			tr[ p ].hs += k.hs * tr[ p ].sum;//会让历史区间和加上k.hs个区间和 
			if(tag[ p ].cov) tag[ p ].cst += k.hs * tag[ p ].cov;//如果当前区间被覆盖,那我们不用管原来区间和,历史区间和就弱化成了常数 
			else tag[ p ].hs += k.hs;//否则就加到历史区间和的tag中 
		}
		if(k.cst){//如果这个区间有常数 
			tr[ p ].hs += k.cst * (r - l + 1);//让历史区间和加上常数 
			tag[ p ].cst += k.cst;//常数也要累加 
		}
		if(k.cov){//如果这个区间有覆盖 
			tr[ p ].sum = k.cov * (r - l + 1); 
			tag[ p ].cov = k.cov;
		}
		//操作顺序非常重要,必须是k.hs在k.cov前面,因为k.hs维护的显然是在区间覆盖之前的历史区间和
		//因为如果覆盖后才开始统计历史区间和,我们会直接把历史区间和加到常数里,而不是历史区间和
		//而如果我们先进行k.cov操作,我们就丢失了在没覆盖之前维护的值,这样是错误的
	}
	inline void PushDown(int l, int r, int p){
		if(!tag[ p ].cov && !tag[ p ].cst && !tag[ p ].hs) return ;
		int mid = l + r >> 1;
		SmlUpd(l, mid, tag[ p ], ls);
		SmlUpd(mid + 1, r, tag[ p ], rs);
		tag[ p ] = Tag{0, 0, 0};
	}
	inline void modify(int l, int r, int x, int y, ll k, int opt, int p){
		if(x <= l && r <= y){
			if(opt == 1) SmlUpd(l, r, Tag{0, 0, k}, p);
			else SmlUpd(l, r, Tag{k, 0, 0}, p);
			return ;
		}
		PushDown(l, r, p);
		int mid = l + r >> 1;
		if(x <= mid) modify(l, mid, x, y, k, opt, ls);
		if(mid < y) modify(mid + 1, r, x, y, k, opt, rs);
		PushUp(p);
	}
	inline ll query(int l, int r, int x, int y, int p){
		if(x <= l && r <= y) return tr[ p ].hs;
		PushDown(l, r, p);
		int mid = l + r >> 1;
		ll res = 0;
		if(x <= mid) res = query(l, mid, x, y, ls);
		if(mid < y) res += query(mid + 1, r, x, y, rs);
		return res;
	}
	
	inline void modify(int l, int r, ll k, int opt){
		modify(1, n, l, r, k, opt, 1);
	}
	inline ll query(int l, int r){
		return query(1, n, l, r, 1);
	}
	
	#undef ls
	#undef rs
} seg;

inline void mian(){
	read(n), read(Q);
	For(i, 1, n) read(a[ i ]);
	int l, r, x, y, cntq = 0;
	For(i, 1, Q){
		read(l), read(r), read(x), read(y);
		ask[ ++ cntq ] = Ask{l, min(r, x - 1), x - 1, i, -1};
		ask[ ++ cntq ] = Ask{l, min(r, y), y, i, 1};
		// 卡常题,把r和x取min防卡常 
	}
	sort(ask + 1, ask + (Q << 1) + 1);
	s.insert(0);
	r = 1;
	For(i, 1, Q << 1){
		if(ask[ i ].l > ask[ i ].r) continue;// 卡常卡常卡常卡常 
		while(r <= ask[ i ].x){
			int pre = buc[ a[ r ] ];
			if(pre){
				auto it = s.find(pre);
				l = *(-- it); ++ it;
				x = ((++ it) == s.end() ? r : *it); -- it;
				//找到会影响的区间 
				seg.modify(l + 1, pre, x, 1);
				s.erase(it);
			}
			buc[ a[ r ] ] = r;
			s.insert(r);
			seg.modify(r, r, r, 1); //同样找到会影响的区间 
			seg.modify(1, n, 1, 2); //更新历史区间和 
			++ r;
		}
		ans[ ask[ i ].id ] += seg.query(ask[ i ].l, ask[ i ].r) * ask[ i ].k;
	}
	For(i, 1, Q) write(ans[ i ], "\n");
}

inline void init(){

}

int main() {

#ifdef ONLINE_JUDGE
#else
	freopen("data.in", "r", stdin);
//	freopen("data.out", "w", stdout);
#endif

	int T = 1;
	while(T --){
		init();
		mian();
	}

	return 0;
}
posted @ 2023-08-25 20:03  FOX_konata  阅读(21)  评论(0编辑  收藏  举报