CF1824D
我们定义
我们扫描先维护右端点
我们考虑再维护一个01数组
我们发现
在修改之前的局面(图盗自:这里)
我们发现加入
但由于这题很卡常,我们可以发现如果
找到这些位置我们可以用
而我们要找的答案即为右端点
但这只是一个询问的情况,我们发现如果有
但我们发现我们实际上维护了这
看一眼线段树3,我们发现这个“历史和”很符合我们的要求
于是我们只需要把所有询问离线,把
-
区间赋值
-
查询区间历史和
以下说一下怎么维护区间赋值历史区间和线段树,会的可以走了
首先建一棵线段树
朴素的,我们先维护题目要求的
我们显然需要现进行
我们发现这个是不太好维护的,这里引用一个大佬的博客,这里这个队列的形容是很贴切的
我们发现如果当前区间的队列中有
但我们发现如果有了
这时如果队列中有
然后注意标记下传的顺序,
最终复杂度
这道题他左端点的范围是固定的,而比赛这题左端点范围不固定。因此理论上我们应该维护右端点递增的历史区间和,而这个貌似是不可做的
但比赛这题和这题做法类似的原因是:如果一个
代码:
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?