历史版本和/最值

我咋感觉这玩意标记跟矩阵完全不是一个难度的呢。这个套路应该不会再考了,不过还是整理一下。

先看一个问题引入:

给定一个序列 \(a\) 有两种操作,支持以下操作。

  1. \(i\in[l,r],a_i\gets a_i+x\),即区间加

  2. 查询 \(\sum_{i=l}^r b_i\)

每轮操作后 \(i\in[1,n],b_i\gets b_i+a_i\)

因为这玩意不会应该就是不会了,所以也讲不了什么思路 /youl。

区间查询,考虑线段树维护。区间加是简单的,每轮可以在根节点打上一个全局更新 \(b\) 的标记,我感觉如果用标记维护很晕。

矩阵乘法都视作外积(对于 \(A\times B=C\)\(C_{i,j}=\sum_{k=1}^n A_{k,j}\cdot B_{i,k}\))。

考虑节点维护
\(\begin{bmatrix}x\\sum\\len\end{bmatrix}\)
分别是区间当前的和、区间历史和以及区间长度。

区间加 \(k\) 的标记即为:

\[\begin{bmatrix} 1 & 0 & k\\ 0 & 1 & 0\\ 0 & 0 & 1 \end{bmatrix} \]

而更新 \(b\) 的标记即为:

\[\begin{bmatrix} 1 & 0 & 0\\ 1 & 1 & 0\\ 0 & 0 & 1 \end{bmatrix} \]

这是 \(\times +\) 矩阵具有结合律,直接维护标记即可。

code
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define fin(x) freopen(#x".in","r",stdin)
#define fout(x) freopen(#x".out","w",stdout)
#define fr(x) fin(x),fout(x);
#define Fr(x,y) fin(x),fout(y)
#define INPUT(_1,_2,FILE,...) FILE
#define IO(...) INPUT(__VA_ARGS__,Fr,fr)(__VA_ARGS__)
using namespace std;
using namespace __gnu_pbds;
#define mp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define cfast ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
#define ll long long
#define ull unsigned long long
#define intz(x,y) memset((x),(y),sizeof((x)))
char *p1,*p2,buf[100000];
#define nc() (p1==p2 && (p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
#define tup(x) array<int,(x)>
#define len(x) (t[(x)].r-t[(x)].l+1)
inline ll read(){
    ll x=0,f=1;char ch=nc();
    while(ch<48||ch>57){if(ch=='-')f=-1;ch=nc();}
    while(ch>=48&&ch<=57)x=x*10+ch-48,ch=nc();
   	return x*f;
}
const int N=1e5+5;
int a[N];
struct matrix{
	ll w[3][3];
	matrix(){w[0][0]=w[0][1]=w[0][2]=w[1][1]=w[1][2]=w[2][2]=0;}
	void init(){w[0][0]=w[1][1]=w[2][2]=1,w[0][1]=w[0][2]=w[1][2]=0;}
	matrix(int fl){
		intz(w,0);
		if(!fl)
			w[0][0]=w[0][1]=w[1][1]=w[2][2]=1;
		else w[0][0]=w[1][1]=w[2][2]=1,w[1][2]=fl;
	}
	void print(){
		for(int i=0;i<3;i++,cout<<'\n')
			for(int j=0;j<3;j++)cerr<<w[i][j]<<' ';
		cerr<<"*****************\n";
	}
	matrix operator*(matrix x){
		matrix res;
		res.w[0][0]=w[0][0]*x.w[0][0],
		res.w[0][1]=w[0][1]*x.w[0][0]+w[1][1]*x.w[0][1],
		res.w[0][2]=w[0][2]*x.w[0][0]+w[1][2]*x.w[0][1]+w[2][2]*x.w[0][2],
		res.w[1][1]=w[1][1]*x.w[1][1],
		res.w[1][2]=w[1][2]*x.w[1][1]+w[2][2]*x.w[1][2],
		res.w[2][2]=w[2][2]*x.w[2][2];
		return res;
	}	
};
struct node{int l,r;ll s,x;matrix laz;bool fl;}t[N<<2];
void pushup(int u){t[u].x=t[u<<1].x+t[u<<1|1].x,t[u].s=t[u<<1].s+t[u<<1|1].s;}
void build(int u,int l,int r){
	t[u]={l,r},t[u].laz.init(),t[u].fl=0;if(l==r)return t[u].x=a[l],void();
	int mid=l+r>>1;build(u<<1,l,mid),build(u<<1|1,mid+1,r);pushup(u);
}
void add(int u,matrix x){
	t[u].laz=t[u].laz*x,t[u].fl=1;
	t[u].s=t[u].s*x.w[0][0]+t[u].x*x.w[0][1]+len(u)*x.w[0][2],
	t[u].x=t[u].x*x.w[1][1]+len(u)*x.w[1][2];
}
void pd(int u){
	if(t[u].fl)
		add(u<<1,t[u].laz),add(u<<1|1,t[u].laz),
		t[u].laz.init(),t[u].fl=0;
}
void upd(int u,int l,int r,matrix x){
	if(t[u].l>=l&&t[u].r<=r)return add(u,x);
	int mid=t[u].l+t[u].r>>1;pd(u);
	if(l<=mid)upd(u<<1,l,r,x);if(r>mid)upd(u<<1|1,l,r,x);
	pushup(u);
}
ll query(int u,int l,int r){
	if(t[u].l>=l&&t[u].r<=r)return t[u].s;
	int mid=t[u].l+t[u].r>>1;ll res=0;pd(u);
	if(l<=mid)res+=query(u<<1,l,r);if(r>mid)res+=query(u<<1|1,l,r);
	return res; 
}
inline void UesugiErii(){
	int n,m;cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>a[i];
	build(1,1,n),add(1,matrix(0));
	for(;m;--m,add(1,matrix(0))){
		int op,l,r,x;cin>>op>>l>>r;
		if(op==1)cin>>x,(x?upd(1,l,r,matrix(x)),1:0);
		else cout<<query(1,l,r)<<'\n';
	}
}
signed main(){
	//IO();
	cfast;
	int _=1;//cin>>_;
	for(;_;_--)UesugiErii();
	return 0;
}

代码的矩阵有点出入 \(x,sum\) 的位置掉换了一下,并且卡常将矩乘展开了。

· 一般历史版本和的题目都有标志 “对于所有 \(l\le l'\le r'\le r\) 记录贡献”。

历史和

CF997E Good Subsegments

连续段套路的转成 \(\max-\min=r-l\to \max-\min-r+l=0\)

考虑对 \(r\) 做扫描线维护每个 \(l\)\(x=\max([l,r])-\min([l,r])-r+l\),维护最值也是可以套路的单调栈维护,找到 \(a_r\) 能更新最值的区间相当于区间加。\(r\) 每向右移对应的 \(x\) 全局 \(-1\)

现在的问题就是要计数 0 在 \([l,r]\) 内历史出现次数。0 的历史出现次数相当难维护,每个标记下放的时候对 \(x\) 是否为 0 都不一样,不满足结合律。

发现树根一定为 0,所以等价于全局最小值历史出现次数。

当且仅当下放 \(x\) 修改标记后,\(x_{ls/rs}=x_u\) 时才下放贡献标记(只有满足此条件在未修改的这段时间中才满足 \(u\) 为全局最小值的同时子节点也为全局最小值)。

因为 \(x\) 的修改标记下传不影响 \(c\) 的值,所以这两个标记互相没有影响,就没必要写矩阵了。

code
// Problem: Good Subsegments
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF997E
// Memory Limit: 500 MB
// Time Limit: 7000 ms
// 
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define mp make_pair
#define fi first
#define se second
#define pii pair<int,int>
const int N=2e5+5;int a[N],z[N],tp,Z[N],Tp,ans[N];vector<pii>g[N];
struct node{int l,r,x,c,s,laz,ls;}t[N<<2];
void pushup(int u){
	t[u].x=min(t[u<<1].x,t[u<<1|1].x);
	t[u].c=(t[u<<1].x==t[u].x)*t[u<<1].c+(t[u<<1|1].x==t[u].x)*t[u<<1|1].c;
	t[u].s=t[u<<1].s+t[u<<1|1].s;
}
void build(int u,int l,int r){
	t[u]={l,r};if(l==r)return t[u].x=l,t[u].c=1,void();
	int mid=l+r>>1;build(u<<1,l,mid),build(u<<1|1,mid+1,r);pushup(u);
}
void add(int u,int x){t[u].x+=x,t[u].laz+=x;}
void upd(int u,int x){t[u].s+=t[u].c*x,t[u].ls+=x;}
void pd(int u){
	upd(u<<1,t[u].ls),upd(u<<1|1,t[u].ls);
	add(u<<1,t[u].laz),add(u<<1|1,t[u].laz);
	t[u].laz=t[u].ls=0;
}
void update(int u,int l,int r,int d,bool f){
	if(t[u].l>=l&&t[u].r<=r)return !f?add(u,d):upd(u,d);
	int mid=t[u].l+t[u].r>>1;pd(u);
	if(l<=mid)update(u<<1,l,r,d,f);if(r>mid)update(u<<1|1,l,r,d,f);pushup(u);
}
int query(int u,int l,int r){
	if(t[u].l>=l&&t[u].r<=r)return t[u].s;
	int mid=t[u].l+t[u].r>>1,s=0;pd(u);
	if(l<=mid)s+=query(u<<1,l,r);if(r>mid)s+=query(u<<1|1,l,r);return s;
}
signed main(){
	int n;cin>>n;for(int i=1;i<=n;i++)cin>>a[i];
	build(1,1,n);
	int q;cin>>q;for(int i=1,l,r;i<=q;i++)cin>>l>>r,g[r].push_back(mp(l,i));
	for(int i=1;i<=n;i++){
		for(;tp&&a[i]>a[z[tp]];--tp)
			update(1,z[tp-1]+1,z[tp],a[i]-a[z[tp]],0);
		z[++tp]=i;
		for(;Tp&&a[i]<a[Z[Tp]];--Tp)
			update(1,Z[Tp-1]+1,Z[Tp],a[Z[Tp]]-a[i],0);
		Z[++Tp]=i;
		add(1,-1),upd(1,1);
		for(pii j:g[i])ans[j.se]=query(1,j.fi,i);
	}
	for(int i=1;i<=q;i++)cout<<ans[i]<<endl;
	return 0;
}

因为是古早代码改的,所以可能有点丑。

P9990 [Ynoi Easy Round 2023] TEST_90Prof. Pang's sequence

同样的考虑对 \(r\) 扫描线,维护 \(l\)\(x\) 为出现过的数个数的奇偶性,那么相当于求 \(x_{[l,r]}\) 的历史版本和。

考虑扫描线右移,新增 \(a_r\) 的影响,即 \(l\in[pre_{a_r},r]\) 的区间的 \(x\) 奇偶性取反。

维护区间内 \(x\) 为奇数的个数 \(c\)\(c\) 的历史版本和 \(s\),维护:

\[\begin{bmatrix} s\\ c\\ len \end{bmatrix} \]

取反 \(x\) 相当于乘上:

\[\begin{bmatrix} 1 & 0 & 0\\ 0 & -1 & 1\\ 0 & 0 & 1 \end{bmatrix} \]

更新历史版本和为乘上:

\[\begin{bmatrix} 1 & 1 & 0\\ 0 & 1 & 0\\ 0 & 0 & 1 \end{bmatrix} \]

还有一种方法是维护 \(x\) 分别为奇数/偶数的个数:

\[\begin{bmatrix} s\\ c0\\ c1 \end{bmatrix} \]

取反就是乘上:

\[\begin{bmatrix} 1 & 0 & 0\\ 0 & 0 & 1\\ 0 & 1 & 0 \end{bmatrix} \]

更新历史版本和乘上:

\[\begin{bmatrix} 1 & 0 & 1\\ 0 & 1 & 0\\ 0 & 0 & 1 \end{bmatrix} \]

不过第一种的常数貌似较小,因为矩阵较多处总为 0 不用更新。

code
// Problem: G. Prof. Pang's sequence
// Contest: Codeforces - 2020 ICPC Asia East Continent Final
// URL: https://codeforces.com/gym/103069/problem/G
// Memory Limit: 256 MB
// Time Limit: 3000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
#define cfast ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
#define pii pair<int,int>
#define fi first
#define se second
#define ll long long
char *p1,*p2,buf[1000000];
#define nc() (p1==p2 && (p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
#define tup(x) array<int,(x)>
inline ll read(){
    ll x=0,f=1;char ch=nc();
    while(ch<48||ch>57){if(ch=='-')f=-1;ch=nc();}
    while(ch>=48&&ch<=57)x=x*10+ch-48,ch=nc();
   	return x*f;
}
const int N=1e6+5;
int p[N],f[N],a[N];ll ans[N];vector<pii>g[N];
struct matrix{
	ll w[3][3];
	matrix(){w[0][0]=w[0][1]=w[0][2]=w[1][0]=w[1][1]=w[1][2]=w[2][0]=w[2][1]=w[2][2]=0;}
	inline void init(){w[0][1]=w[0][2]=w[1][0]=w[1][2]=w[2][0]=w[2][1]=0,w[0][0]=w[1][1]=w[2][2]=1;}
	inline void mk(int op){init(),(!op?(w[1][1]=w[2][2]=0,w[1][2]=w[2][1]=1):w[0][2]=1);}
	inline matrix operator*(const matrix x){
		matrix res;
		res.w[0][0]=w[0][0]*x.w[0][0]+w[1][0]*x.w[0][1]+w[2][0]*x.w[0][2],
		res.w[0][1]=w[0][1]*x.w[0][0]+w[1][1]*x.w[0][1]+w[2][1]*x.w[0][2],
		res.w[0][2]=w[0][2]*x.w[0][0]+w[1][2]*x.w[0][1]+w[2][2]*x.w[0][2],
		res.w[1][1]=w[1][1]*x.w[1][1]+w[2][1]*x.w[1][2],
		res.w[1][2]=w[1][2]*x.w[1][1]+w[2][2]*x.w[1][2],
		res.w[2][1]=w[1][1]*x.w[2][1]+w[2][1]*x.w[2][2],
		res.w[2][2]=w[1][2]*x.w[2][1]+w[2][2]*x.w[2][2];
		return res;
	}
};
struct node{int l,r,c0,c1;ll s;bool fl;matrix laz;}t[N<<2];
inline void pushup(int u){
	t[u].c0=t[u<<1].c0+t[u<<1|1].c0;
	t[u].c1=t[u<<1].c1+t[u<<1|1].c1;
	t[u].s=t[u<<1].s+t[u<<1|1].s;
}
void build(int u,int l,int r){
	t[u]={l,r},t[u].laz.init();if(l==r)return t[u].c0=1,t[u].c1=t[u].s=0,void();
	int mid=l+r>>1;build(u<<1,l,mid),build(u<<1|1,mid+1,r);pushup(u);
}
inline void add(int u,matrix x){
	ll S=t[u].s,C0=t[u].c0,C1=t[u].c1;
	t[u].laz=t[u].laz*x,t[u].fl=1;
	t[u].s=S*x.w[0][0]+C0*x.w[0][1]+C1*x.w[0][2];
	t[u].c0=S*x.w[1][0]+C0*x.w[1][1]+C1*x.w[1][2];
	t[u].c1=S*x.w[2][0]+C0*x.w[2][1]+C1*x.w[2][2];
}
inline void pd(int u){if(t[u].fl)add(u<<1,t[u].laz),add(u<<1|1,t[u].laz),t[u].laz.init(),t[u].fl=0;}
void update(int u,int l,int r,matrix x){
	if(t[u].l>=l&&t[u].r<=r)return add(u,x);
	int mid=t[u].l+t[u].r>>1;pd(u);
	if(l<=mid)update(u<<1,l,r,x);if(r>mid)update(u<<1|1,l,r,x);
	pushup(u);
}
ll sum;
void query(int u,int l,int r){
	if(t[u].l>=l&&t[u].r<=r)return sum+=t[u].s,void();
	int mid=t[u].l+t[u].r>>1;pd(u);
	if(l<=mid)query(u<<1,l,r);if(r>mid)query(u<<1|1,l,r); 
}
signed main(){
	int n;n=read();
	for(int i=1;i<=n;i++)a[i]=read(),p[i]=f[a[i]],f[a[i]]=i;
	int m;m=read();
	for(int i=1,l,r;i<=n;i++)
		l=read(),r=read(),g[r].push_back(mp(l,i));
	build(1,1,n);matrix W,H;W.mk(0),H.mk(1);
	for(int i=1;i<=n;i++){
		update(1,p[i]+1,i,W);add(1,H);
		for(pii j:g[i])sum=0,query(1,j.fi,i),ans[j.se]=sum;
	}
	for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
	return 0;
}

写的是第二种,这份代码常数过大只有 80 pts。卡卡就过了 /fendou。

历史最值

历史最值实际上只需要知道 \(\min+,\max+,\max\times,\min\times\) 矩阵均具有结合律,详见 快速证明广义矩阵乘法满足结合律,但是我感觉这个只证了充分性啊 /yun。

P4314 CPU 监控

定义广义矩阵 \(C_{i,j}=\max_{k=1}^n \{A_{k,j}+ B_{i,k}\}\)

线段树维护:

\[\begin{bmatrix} mx\\ s\\ 0 \end{bmatrix} \]

表示当前区间最大值,区间历史最大值,0 在区间赋值起作用。

区间加 \(k\) 是简单的,乘上:

\[\begin{bmatrix} k & -\infin & -\infin\\ -\infin & 0 & -\infin\\ -\infin & -\infin & 0 \end{bmatrix} \]

区间赋值 \(k\) 就是乘上:

\[\begin{bmatrix} -\infin & -\infin & k\\ -\infin & 0 & -\infin\\ -\infin & -\infin & 0 \end{bmatrix} \]

每轮更新历史最大值就是乘上:

\[\begin{bmatrix} 0 & -\infin & -\infin\\ 0 & 0 & -\infin\\ -\infin & -\infin & 0 \end{bmatrix} \]

code
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define fin(x) freopen(#x".in","r",stdin)
#define fout(x) freopen(#x".out","w",stdout)
#define fr(x) fin(x),fout(x);
#define Fr(x,y) fin(x),fout(y)
#define INPUT(_1,_2,FILE,...) FILE
#define IO(...) INPUT(__VA_ARGS__,Fr,fr)(__VA_ARGS__)
using namespace std;
using namespace __gnu_pbds;
#define mp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define cfast ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
#define ll long long
#define ull unsigned long long
#define intz(x,y) memset((x),(y),sizeof((x)))
char *p1,*p2,buf[100000];
#define nc() (p1==p2 && (p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
#define tup(x) array<int,(x)>
inline ll read(){
    ll x=0,f=1;char ch=nc();
    while(ch<48||ch>57){if(ch=='-')f=-1;ch=nc();}
    while(ch>=48&&ch<=57)x=x*10+ch-48,ch=nc();
   	return x*f;
}
//void write(int x){cout<<x<<' ';}
//void write(pii x){cout<<"P("<<x.fi<<','<<x.se<<")\n";}
//void write(vector<auto>x){for(auto i:x)write(i);cout<<'\n';}
//void write(auto *a,int l,int r){for(int i=l;i<=r;i++)write(a[i]);cout<<'\n';}
inline ll lowbit(ll x){return x&-x;}
#define pcount(x) __builtin_popcount(x)
inline void cmx(auto &x,ll y){if(y>x)x=y;}
inline void cmn(auto &x,ll y){if(y<x)x=y;}
const int mod=998244353;
#define int ll
ll qp(ll x,int y){ll res=1;for(;y;x=x*x%mod,y>>=1)if(y&1)res=res*x%mod;return res;}
const int N=1e5+5,inf=-1e13;
int max(vector<int>w){int res=inf;for(int i:w)cmx(res,i);return res;}
struct matrix{
	int w[3][3];
	matrix(){
		for(int i=0;i<3;i++)
			for(int j=0;j<3;j++)w[i][j]=inf;
	}
	void init(){
		for(int i=0;i<3;i++)
			for(int j=0;j<3;j++)w[i][j]=inf;
		w[0][0]=w[1][1]=w[2][2]=0;
	}
	void mk(int k,int op){init();
		if(!op)w[0][0]=k;
		else if(op==1)w[0][0]=inf,w[0][2]=k;
		else w[1][0]=0;
	}
	inline matrix operator*(const matrix x){
		matrix res;
		for(int i=0;i<3;i++)
			for(int j=0;j<3;j++)
				for(int k=0;k<3;k++)
					cmx(res.w[i][j],w[k][j]+x.w[i][k]);
		return res;
	}
};
int a[N];
struct node{int l,r,x,mx;matrix laz;}t[N<<2];
inline void pushup(int u){t[u].x=max(t[u<<1].x,t[u<<1|1].x),t[u].mx=max(t[u<<1].mx,t[u<<1|1].mx);}
void build(int u,int l,int r){
	t[u]={l,r},t[u].laz.init();int mid=l+r>>1;
	if(l==r)return t[u].x=t[u].mx=a[l],void();
	build(u<<1,l,mid),build(u<<1|1,mid+1,r);pushup(u);
}
inline void add(int u,matrix x){
	t[u].laz=t[u].laz*x;int X=t[u].x,W=t[u].mx;
	t[u].x=max({X+x.w[0][0],W+x.w[0][1],x.w[0][2]}),
	t[u].mx=max({X+x.w[1][0],W+x.w[1][1],x.w[1][2]});
}
void pd(int u){add(u<<1,t[u].laz),add(u<<1|1,t[u].laz),t[u].laz.init();}
void upd(int u,int l,int r,matrix x){
	if(t[u].l>=l&&t[u].r<=r)return add(u,x);
	int mid=t[u].l+t[u].r>>1;pd(u);
	if(l<=mid)upd(u<<1,l,r,x);if(r>mid)upd(u<<1|1,l,r,x);
	pushup(u);
}
int ask(int u,int l,int r,int op){
	if(t[u].l>=l&&t[u].r<=r)return (!op?t[u].x:t[u].mx);
	int mid=t[u].l+t[u].r>>1,mx=inf;pd(u);
	if(l<=mid)cmx(mx,ask(u<<1,l,r,op));
	if(r>mid)cmx(mx,ask(u<<1|1,l,r,op));
	return mx;
}
inline void UesugiErii(){
	int n;cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	build(1,1,n);
	int q;cin>>q;matrix p;
	while(q--){
		char op;int l,r,x;cin>>op>>l>>r;
		if(op=='Q')cout<<ask(1,l,r,0)<<'\n';
		else if(op=='A')cout<<ask(1,l,r,1)<<'\n';
		else cin>>x,p.mk(x,op=='C'),upd(1,l,r,p);
		p.mk(0,2),add(1,p); 
	}
}
signed main(){
	//IO();
	cfast;
	int _=1;//cin>>_;
	for(;_;_--)UesugiErii();
	return 0;
}
posted @ 2025-12-04 18:04  Uesugi1  阅读(12)  评论(2)    收藏  举报