历史和线段树
历史和的定义是每一次操作后每个位置上的历史和 \(h_i\) 都会累加上一个当前位置的 \(a_i\)。粗略的说就是给每一位加上了一个时间维 \(t\)。设 \(a_{i,t}\) 表示位置 \(i\) 上在第 \(t\) 次操作后的值,那么某次操作后的历史和 \(h_{i,t}=\sum_{j=1}^ta_{i,j}\)。
LOJ #193. 线段树历史和
我们从这道模板题看起。
修改:区间加。查询:区间历史和。
solution1
考虑到我们如果操作区间 \([l,r]\),那么别的没有被操作到的点的值会自动增加。考虑维护这么一个会自动增加的值。
设 \(c_u=h_u-t\times a_u\)。如果我们对于每个节点去维护其当前的真实值 \(a_u\) 和参数 \(c_u\) 就可以了。查询的时候只需要当前的时间 \(t\) 即可。维护 \(c_u\) 是好维护的。我们只需要保证加 \(w\) 之后 \(t\gets t+1\) 后会自动有 \(h_u\gets a_u+w\) 即可。显然有 \(c_u\gets c_u-t\times w\)。于是多维护一个区间加即可。
代码是好写的。
点击查看代码
ll b[N];
namespace sgt_his_sum{ //在操作的时候改变 t 的值
ll a[N<<2],c[N<<2],ta[N<<2],tc[N<<2],t;
#define ls (u<<1)
#define rs (u<<1|1)
#define mid ((l+r)>>1)
#define len (r-l+1)
void upd(int u,int l,int r,ll sa,ll sc){a[u]+=len*sa;c[u]+=len*sc;ta[u]+=sa,tc[u]+=sc;}
void push_up(int u){a[u]=a[ls]+a[rs];c[u]=c[ls]+c[rs];}
void push_down(int u,int l,int r){
upd(ls,l,mid,ta[u],tc[u]),upd(rs,mid+1,r,ta[u],tc[u]);
ta[u]=tc[u]=0;
}
void build(int u,int l,int r){
ta[u]=tc[u]=0;if(l==r){a[u]=c[u]=b[l];return;}
build(ls,l,mid),build(rs,mid+1,r);push_up(u);
}
void modify(int u,int l,int r,int ql,int qr,ll w){
if(ql<=l&&r<=qr){upd(u,l,r,w,-1ll*t*w);return;}
push_down(u,l,r);
if(ql<=mid)modify(ls,l,mid,ql,qr,w);
if(qr>mid)modify(rs,mid+1,r,ql,qr,w);
push_up(u);
}
ll query(int u,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr){return c[u]+1ll*t*a[u];}
ll res=0;push_down(u,l,r);
if(ql<=mid)res+=query(ls,l,mid,ql,qr);
if(qr>mid)res+=query(rs,mid+1,r,ql,qr);
return res;
}
}
solution2
注意到上面的方法扩展性相当低下,遇到一些别的操作就爆了(比如区间乘法)。上面方法的本质是给每个时间加一个因子使得 \(c_u\) 的值能够拟合上 \(h_u\)。当操作比较复杂的时候可能构造就比较困难或者根本构造不出来。
考虑一些更加本质的方法。注意到对于每一个节点,我们可以写出一个形如操作序列的东西出来。我们将自己累加的操作称为 \(upd\),那么时间变化就相当于全局插入一个 \(upd\) 操作。修改就是对应节点插入区间加操作。容易注意到这个东西扩展性更强。
具体的维护当然不可能维护一整个操作序列。我们只能记录每一个点有多少个 \(upd\) 标记。我们考虑标记下放的过程就是将整个操作序列拼在儿子的操作序列后面,因此我们只需要知道当前的操作序列对于儿子节点的贡献是多少就可以了。
我们设当前节点的操作序列有 \(cnt\) 个 \(upd\) 标记,当其下放到一个儿子 \(v\) 的时候,这些标记的贡献就是下放前 \(v\) 的区间和 \(a_v\) 乘上 \(cnt\) 加上这些标记的“历史和”,也就是我们将贡献划分为了两部分。于是我们再维护一个标记的历史和即可。
代码也不是非常难写。但是需要注意的是,区间加的时候只能更改 \(tag\) 和 \(a\) 的值,因为修改更改 \(h\) 数组的值是在这一轮加完以后由 \(upd\) 标记来做的,不需要自己去更改。
点击查看代码
ll b[N];
namespace sgt_his_sum{ //切换时间的时候调用 nxt_tim 即可
ll t,a[N<<2],cnt[N<<2],h[N<<2],tag[N<<2],th[N<<2];
#define ls (u<<1)
#define rs (u<<1|1)
#define mid ((l+r)>>1)
#define len (ll)(r-l+1)
void upd(int u,int l,int r,ll st,ll w,int num){
h[u]+=num*a[u]+len*st;th[u]+=tag[u]*num+st;cnt[u]+=num;a[u]+=w*len;tag[u]+=w;
}
void push_up(int u){
a[u]=a[ls]+a[rs];h[u]=h[ls]+h[rs];
}
void push_down(int u,int l,int r){
upd(ls,l,mid,th[u],tag[u],cnt[u]),upd(rs,mid+1,r,th[u],tag[u],cnt[u]);
th[u]=tag[u]=cnt[u]=0;
}
void build(int u,int l,int r){
if(l==r){a[u]=h[u]=b[l];return;}
build(ls,l,mid),build(rs,mid+1,r);push_up(u);
}
void modify(int u,int l,int r,int ql,int qr,ll w){
if(ql<=l&&r<=qr){upd(u,l,r,0,w,0);return;}
push_down(u,l,r);
if(ql<=mid)modify(ls,l,mid,ql,qr,w);
if(qr>mid)modify(rs,mid+1,r,ql,qr,w);
push_up(u);
}
ll query(int u,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr){return h[u];}
ll res=0;push_down(u,l,r);
if(ql<=mid)res+=query(ls,l,mid,ql,qr);
if(qr>mid)res+=query(rs,mid+1,r,ql,qr);
return res;
}
void nxt_tim(){t++;upd(1,1,n,0,0,1);}
}
P9990 [Ynoi Easy Round 2023] TEST_90
历史和线段树可以比较好的做一些区间子区间的问题。
修改:没有。查询:区间中颜色种数为奇数的子区间个数。
以这道题为例说一下历史和维护区间子区间的大概做法。首先离线下来扫描线。
然后扫序列维。统计当前点作为右端点,每一个左端点的贡献。然后历史和就会自动将以当前点为右端点前面每个位置为左端点的贡献记录下来。
于是扫描线上每一个位置就代表着其作为左端点时所有区间的答案,所以区间子区间的贡献就挂在右端点然后区间查询即可。
我们考虑这道题扫到某一个右端点 \(r\) 时每一个左端点的贡献是什么。注意到对于每个左端点 \(l\),如果 \([l,r]\) 的颜色数是奇数当前时刻 \(l\) 的贡献就是 1,否则就是 0。于是相当于一个全局覆盖。但是历史和其实不是很好做区间覆盖的问题。不过注意到只会覆盖 1,因此这个历史和等价于普通线段树在对应位置上加 1。
于是就是全局对颜色数是奇数的位置加 1 即可。这个怎么做呢?考虑加入一个数 \(a_r\) 的时候,上一个颜色为 \(a_r\) 的位置 \(lst_r\)。这个是可以方便的预处理的。那么区间 \((lst_r,r]\) 的颜色种类数奇偶性都会翻转。于是我们考虑维护两棵线段树,翻转奇偶性就是交换对应位置的节点,加就只在奇数的那棵树上加就可以了。但是需要注意的是查询的时候需要两棵树都查,因为这个只代表当前时刻其奇偶性,不代表前面就没有贡献了。
code
代码是简单的。
点击查看代码
#include<bits/stdc++.h>
bool Mbe;
using namespace std;
#define ll long long
// namespace FIO{
// template<typename P>
// inline void read(P &x){P res=0,f=1;char ch=getchar();while(ch<'0' || ch>'9'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0' && ch<='9'){res=(res<<3)+(res<<1)+(ch^48);ch=getchar();}x=res*f;}
// template<typename Ty,typename ...Args>
// inline void read(Ty &x,Args &...args) {read(x);read(args...);}
// inline void write(ll x) {if(x<0ll)putchar('-'),x=-x;static int sta[35];int top = 0;do {sta[top++] = x % 10ll, x /= 10ll;} while (x);while (top) putchar(sta[--top] + 48);}
// }
// using FIO::read;using FIO::write;
const int N=1e6+7;
ll b[N];int n,m;
namespace sgt{ //切换时间的时候调用 nxt_tim 即可
ll t;int swt[N<<2];
#define ls (u<<1)
#define rs (u<<1|1)
#define mid ((l+r)>>1)
struct node{
ll a[N<<2],tag[N<<2],len[N<<2];
void upd(int u,ll w){a[u]+=w*len[u];tag[u]+=w;}
void push_up(int u){a[u]=a[ls]+a[rs];len[u]=len[ls]+len[rs];}
void push_down(int u){upd(ls,tag[u]),upd(rs,tag[u]);tag[u]=0;}
}x,y; //奇,偶
void upd(int u){swap(x.a[u],y.a[u]);swap(x.tag[u],y.tag[u]);swap(x.len[u],y.len[u]);swt[u]^=1;}
void push_down(int u){if(swt[u])upd(ls),upd(rs),swt[u]=0;}
void build(int u,int l,int r){
if(l==r){y.len[u]=1;return;}
build(ls,l,mid),build(rs,mid+1,r);
x.push_up(u);y.push_up(u);
}
void modify1(int u,int l,int r,int ql,int qr){ //区间异或
if(ql<=l&&r<=qr){upd(u);return;}
push_down(u);x.push_down(u);y.push_down(u);
if(ql<=mid)modify1(ls,l,mid,ql,qr);
if(qr>mid)modify1(rs,mid+1,r,ql,qr);
x.push_up(u);y.push_up(u);
}
void modify2(int u,int l,int r,int ql,int qr,ll w){ //x 区间加 1
if(ql<=l&&r<=qr){x.upd(u,w);return;}
push_down(u);x.push_down(u);y.push_down(u);
if(ql<=mid)modify2(ls,l,mid,ql,qr,w);
if(qr>mid)modify2(rs,mid+1,r,ql,qr,w);
x.push_up(u);y.push_up(u);
}
ll query(int u,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr){return x.a[u]+y.a[u];}
ll res=0;push_down(u);x.push_down(u),y.push_down(u);
if(ql<=mid)res+=query(ls,l,mid,ql,qr);
if(qr>mid)res+=query(rs,mid+1,r,ql,qr);
return res;
}
}
#define pii pair<int,int>
vector <pii> que[N];
ll ans[N];int lst[N],loc[N];
bool Med;
signed main(){ //记得给 y 的 len 赋初值
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin>>n;for(int i=1;i<=n;i++)cin>>b[i],lst[i]=loc[b[i]],loc[b[i]]=i;
sgt::build(1,1,n);
cin>>m;for(int i=1,l,r;i<=m;i++)cin>>l>>r,que[r].push_back({l,i});
for(int i=1;i<=n;i++){
sgt::modify1(1,1,n,lst[i]+1,i);sgt::modify2(1,1,n,1,i,1);
for(int j=0;j<que[i].size();j++){int l=que[i][j].first,id=que[i][j].second;ans[id]=sgt::query(1,1,n,l,i);}
}
for(int i=1;i<=m;i++)cout<<ans[i]<<'\n';
cerr<<'\n'<<1e3*clock()/CLOCKS_PER_SEC<<"ms\n";
cerr<<'\n'<<fabs(&Med-&Mbe)/1048576.0<<"MB\n";
return 0;
}
P10637 BZOJ4262 Sum
这个题就是正宗的用历史和线段树维护区间子区间的模板了。
修改:没有。查询:每次询问给定 \(l1,l2,r1,r2\),求
显然 \(\min\) 和 \(\max\) 的部分完全独立,可以分开算,而且显然比较类似,因此以求 \(\max\) 为例。
注意到划定了左右端点的范围。左端点的限制我们是可以简单做的,因为我们在查询的时候就是询问区间的点作为左端点,只不过我们一般都是查询的后缀,现在变成一段普通的区间而已。关键是右端点的限制如何处理。注意到在这个贡献显然有可差分性,因此我们用 \(r_2\) 处的贡献减去 \(l_2-1\) 处的贡献即可。也就是正常的扫描线的思路。
至于历史和线段树如何维护 \(\max\),考虑一个左端点 \(l\) 在什么情况下贡献与上一个右端点的不同。显然有 \(a_r>a_{[l,r-1]}\),也就是后缀最大值改变的位置,我们将其贡献覆盖成 \(a_r\)。但是我们还是不会做覆盖,注意到显然可以用单调栈来维护后缀最大值覆盖的区间,然后就可以转变成对这些区间区间加,然后做完了。
code
注意维护 \(\min\) 和 \(\max\) 之间需要清空。
点击查看代码
#include<bits/stdc++.h>
bool Mbe;
using namespace std;
#define ll long long
//namespace FIO{
// template<typename P>
// inline void read(P &x){P res=0,f=1;char ch=getchar();while(ch<'0' || ch>'9'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0' && ch<='9'){res=(res<<3)+(res<<1)+(ch^48);ch=getchar();}x=res*f;}
// template<typename Ty,typename ...Args>
// inline void read(Ty &x,Args &...args) {read(x);read(args...);}
// inline void write(ll x) {if(x<0ll)putchar('-'),x=-x;static int sta[35];int top = 0;do {sta[top++] = x % 10ll, x /= 10ll;} while (x);while (top) putchar(sta[--top] + 48);}
//}
//using FIO::read;using FIO::write;
const int N=1e5+7;
ll b[N];int n,m;
void getb(){
const int mod = 1e9;
long long fst = 1023, sec = 1025;
for (int i = 1; i <= 100000; i++) {
b[i] = fst ^ sec;
fst = fst * 1023 % mod;
sec = sec * 1025 % mod;
}
}
namespace sgt{ //切换时间的时候调用 nxt_tim 即可
ll t,a[N<<2],cnt[N<<2],h[N<<2],tag[N<<2],th[N<<2];
#define ls (u<<1)
#define rs (u<<1|1)
#define mid ((l+r)>>1)
#define len (ll)(r-l+1)
void upd(int u,int l,int r,ll st,ll w,int num){
h[u]+=num*a[u]+len*st;th[u]+=tag[u]*num+st;cnt[u]+=num;a[u]+=w*len;tag[u]+=w;
}
void push_up(int u){
a[u]=a[ls]+a[rs];h[u]=h[ls]+h[rs];
}
void push_down(int u,int l,int r){
upd(ls,l,mid,th[u],tag[u],cnt[u]),upd(rs,mid+1,r,th[u],tag[u],cnt[u]);
th[u]=tag[u]=cnt[u]=0;
}
void build(int u,int l,int r){
if(l==r){a[u]=h[u]=b[l];return;}
build(ls,l,mid),build(rs,mid+1,r);push_up(u);
}
void modify(int u,int l,int r,int ql,int qr,ll w){
if(ql<=l&&r<=qr){upd(u,l,r,0,w,0);return;}
push_down(u,l,r);
if(ql<=mid)modify(ls,l,mid,ql,qr,w);
if(qr>mid)modify(rs,mid+1,r,ql,qr,w);
push_up(u);
}
ll query(int u,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr){return h[u];}
ll res=0;push_down(u,l,r);
if(ql<=mid)res+=query(ls,l,mid,ql,qr);
if(qr>mid)res+=query(rs,mid+1,r,ql,qr);
return res;
}
void nxt_tim(){t++;upd(1,1,n,0,0,1);}
void init(){
t=0;for(int i=1;i<=(n<<2);i++){a[i]=cnt[i]=h[i]=tag[i]=th[i]=0;}
}
}
#define pii pair<int,int>
struct node{int xl,xr,yl,yr,k,id;};
vector <node> que[N];
ll ans[N],top;
pii stk[N];
void calcmx(){
for(int i=1;i<=n;i++){
int l=i;
while(top&&b[stk[top].second]<b[i])l=stk[top].first,sgt::modify(1,1,n,stk[top].first,stk[top].second,b[i]-b[stk[top].second]),top--;
stk[++top]={l,i};sgt::modify(1,1,n,i,i,b[i]);sgt::nxt_tim();
for(auto [xl,xr,yl,yr,k,id]:que[i]){
if(k==1){ans[id]+=sgt::query(1,1,n,xl,xr);}
else{ans[id]-=sgt::query(1,1,n,xl,xr);}
}
}
}
void calcmi(){
for(int i=1;i<=n;i++){
int l=i;
while(top&&b[stk[top].second]>b[i])l=stk[top].first,sgt::modify(1,1,n,stk[top].first,stk[top].second,b[i]-b[stk[top].second]),top--;
stk[++top]={l,i};sgt::modify(1,1,n,i,i,b[i]);sgt::nxt_tim();
for(auto [xl,xr,yl,yr,k,id]:que[i]){
if(k==1){ans[id]-=sgt::query(1,1,n,xl,xr);}
else{ans[id]+=sgt::query(1,1,n,xl,xr);}
}
}
}
bool Med;
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
n=100000;cin>>m;getb();
for(int i=1,xl,xr,yl,yr;i<=m;i++){cin>>xl>>xr>>yl>>yr;que[yl-1].push_back({xl,xr,yl,yr,-1,i});que[yr].push_back({xl,xr,yl,yr,1,i});}
calcmx();top=0;sgt::init();calcmi();
for(int i=1;i<=m;i++)cout<<ans[i]<<'\n';
cerr<<'\n'<<1e3*clock()/CLOCKS_PER_SEC<<"ms\n";
cerr<<'\n'<<fabs(&Med-&Mbe)/1048576.0<<"MB\n";
return 0;
}
P8868 [NOIP2022] 比赛
超级大困难题。
给定数组 \(a,b\)。修改:没有。查询:区间中子区间的贡献和。其中一个子区间 \([l,r]\) 的贡献为 \((\max_{i\in [l,r]}a_i)\times (\max_{i\in [l,r]}b_i)\)。
第一步肯定还是类似的套路扫描线扫序列维然后单调栈维护 \(a\) 和 \(b\) 的后缀最大值序列,然后去将区间覆盖转换为区间加 \(a\) 和 \(b\)。
但是这个乘法显然就不能用普通的区间加之类的直接维护了。考虑多搞几个标记来尝试维护一下。
考虑我们区间加的标记永久化维护的过程是在合并操作序列然后计算操作序列拼在后面对儿子的贡献。我们用类似的思路来维护。
考虑设 \(c\) 表示区间 \(\sum ab\),\(hc\) 表示 \(c\) 的历史和,\(a\) 表示区间 \(a\) 的和,\(ta\) 表示 \(a\) 的区间加标记,\(tha\) 表示区间加标记的历史和,\(thc\) 表示 \(ta\times tb\) 的历史和,其余的定义类似。
注意到 \(hc\) 和 \(thc\) 的维护都是形如 \((a+ta)(b+tb)\) 的形式,于是我们将括号拆开每一部分分开算即可。具体的转移可以看代码。
code
想清楚转移后就和上一道题一模一样了。
点击查看代码
#include<bits/stdc++.h>
bool Mbe;
using namespace std;
#define ll unsigned long long
//namespace FIO{
// template<typename P>
// inline void read(P &x){P res=0,f=1;char ch=getchar();while(ch<'0' || ch>'9'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0' && ch<='9'){res=(res<<3)+(res<<1)+(ch^48);ch=getchar();}x=res*f;}
// template<typename Ty,typename ...Args>
// inline void read(Ty &x,Args &...args) {read(x);read(args...);}
// inline void write(ll x) {if(x<0ll)putchar('-'),x=-x;static int sta[35];int top = 0;do {sta[top++] = x % 10ll, x /= 10ll;} while (x);while (top) putchar(sta[--top] + 48);}
//}
//using FIO::read;using FIO::write;
const int N=2.5e5+7;
#define pii pair<int,int>
ll sa[N],sb[N];int n,tota,totb;
namespace sgt{
int t;ll a[N<<2],b[N<<2],c[N<<2],hc[N<<2],ta[N<<2],tb[N<<2],tha[N<<2],thb[N<<2],thc[N<<2],cnt[N<<2];
#define ls (u<<1)
#define rs (u<<1|1)
#define mid ((l+r)>>1)
#define len ((ll)(r-l+1))
void updtag(int u,int l,int r,ll wta,ll wtb){
c[u]+=b[u]*wta;a[u]+=wta*len;ta[u]+=wta;
c[u]+=a[u]*wtb;b[u]+=wtb*len;tb[u]+=wtb;
}
void updh(int u,int l,int r,ll wtha,ll wthb,ll wthc,ll num){
hc[u]+=c[u]*num+a[u]*wthb+b[u]*wtha+len*wthc;
tha[u]+=num*ta[u]+wtha;thb[u]+=num*tb[u]+wthb;
thc[u]+=ta[u]*tb[u]*num+ta[u]*wthb+tb[u]*wtha+wthc;
cnt[u]+=num;
}
void push_up(int u){c[u]=c[ls]+c[rs];a[u]=a[ls]+a[rs];b[u]=b[ls]+b[rs];hc[u]=hc[ls]+hc[rs];}
void push_down(int u,int l,int r){
updh(ls,l,mid,tha[u],thb[u],thc[u],cnt[u]),updh(rs,mid+1,r,tha[u],thb[u],thc[u],cnt[u]);
updtag(ls,l,mid,ta[u],tb[u]),updtag(rs,mid+1,r,ta[u],tb[u]);
ta[u]=tb[u]=tha[u]=thb[u]=thc[u]=cnt[u]=0;
}
void modify(int u,int l,int r,int ql,int qr,ll wa,ll wb){
if(ql<=l&&r<=qr){updtag(u,l,r,wa,wb);return;}
push_down(u,l,r);
if(ql<=mid)modify(ls,l,mid,ql,qr,wa,wb);
if(qr>mid)modify(rs,mid+1,r,ql,qr,wa,wb);
push_up(u);
}
ll query(int u,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr){return hc[u];}
push_down(u,l,r);ll res=0;
if(ql<=mid)res+=query(ls,l,mid,ql,qr);
if(qr>mid)res+=query(rs,mid+1,r,ql,qr);
return res;
}
void nxt_tim(){t++;updh(1,1,n,0,0,0,1);}
}
ll ans[N];
struct node{ll w;int l,r;}stka[N],stkb[N];
vector <pii> que[N];
bool Med;
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
// freopen("match3.in","r",stdin);
// freopen("match.out","w",stdout);
int ID;cin>>ID>>n;
for(int i=1;i<=n;i++)cin>>sa[i];
for(int i=1;i<=n;i++)cin>>sb[i];
int q;cin>>q;
for(int i=1,l,r;i<=q;i++){cin>>l>>r;que[r].push_back({l,i});}
for(int i=1;i<=n;i++){
int la=i,lb=i;
while(tota&&stka[tota].w<sa[i])sgt::modify(1,1,n,stka[tota].l,stka[tota].r,sa[i]-stka[tota].w,0),la=stka[tota].l,tota--;
while(totb&&stkb[totb].w<sb[i])sgt::modify(1,1,n,stkb[totb].l,stkb[totb].r,0,sb[i]-stkb[totb].w),lb=stkb[totb].l,totb--;
stka[++tota]={sa[i],la,i};stkb[++totb]={sb[i],lb,i};sgt::modify(1,1,n,i,i,sa[i],sb[i]);
sgt::nxt_tim();
for(auto [l,id]:que[i]){ans[id]=sgt::query(1,1,n,l,i);}
}
for(int i=1;i<=q;i++)cout<<ans[i]<<'\n';
cerr<<'\n'<<1e3*clock()/CLOCKS_PER_SEC<<"ms\n";
cerr<<'\n'<<fabs(&Med-&Mbe)/1048576.0<<"MB\n";
return 0;
}

浙公网安备 33010602011771号