题目传送门
题意分析
原题面等价于给定序列 \(a_1,a_2,a_3,\cdots,a_n\) 和 \(b_1,b_2,b_3,\cdots,b_n\) 和 \(q\) 次询问,每次询问给定 \(l,r\),求:
ST 表暴力做是 \(\mathcal O\left(qn^2\right)\) 的,可以得到 8pts 的高分。
设:
则,答案就是:
考虑对 \(r\) 进行扫描线,可以得到转移:
对于询问 \((l_0,r_0)\),答案为:
因此我们在扫描 \(r\) 的过程中,需要维护 \(\displaystyle\sum_{l=l_0}^rx_{l,r}y_{l,r}\) 的历史和。
为了便于讨论,钦定 \(r\) 一定,记为 \(x_l,y_l\),修改就直接做 \(\operatorname{chkmax}\)。钦定 \(r<l\) 时 \(x_l=y_l=0\),我们要求的就是 \(\displaystyle\sum_{i=l_0}^{r_0}x_iy_i\) 的 \([l_0,r_0]\) 版本的历史版本和。
离线后可以差分处理,但是因为我们钦定了初始为 \(0\),因此差不差分都一样。扫到 \(l_0\) 之前 \(l_0\) 右边都是 \(0\),直接求所有版本的历史和即可。
暴力做可以得到 20pts,考虑数据结构维护,区间操作容易想到线段树。
如果是线段树维护 \(\operatorname{chkmax}\) 的话是愚蠢的,因为这样要写吉司机线段树,很难写还有维护很多信息。
因为 \(a,b\) 已知,因此可以提前预处理每一个 \(a_i,b_i\) 能够影响的区间,从而将线段树操作转化为区间覆盖。具体而言,以 \(a_i\) 为例,其覆盖范围为 \([j+1,i]\),\(j\) 为满足 \(j<i\) 的最后一个大于 \(a_i\) 的数。换而言之,实际上 \(x,y\) 分别是 \(a,b\) 的动态后缀最大值。这很容易用单调栈得到。
之后我们需要的线段树操作有:
- \(x_i,y_i\) 区间覆盖
- 查询 \(\displaystyle\sum x_iy_i\) 历史和
重点显然在于 \(\displaystyle\sum x_iy_i\)。考虑线段树维护历史和。
历史和线段树
先考虑矩阵乘法实现的历史和线段树。
矩阵乘法
记 \(\textit{hxy}\) 为 \(\displaystyle\sum x_iy_i\) 历史和,简记 \(\textit{xy}\) 表示 \(x_iy_i\),\(x\) 为 \(x_i\),\(y\) 为 \(y_i\)。因为涉及到区间赋值,需要用到区间长度判断新的区间和,因此把区间长度 \(d\) 丢入矩阵。但是这只是为了转移方便,事实上对于所有的线段树节点,其区间长度 \(d\) 都为定值。
对于求历史和,有转移矩阵:
之后考虑 \(x\) 区间覆盖为 \(k\),有:
同理还有 \(y\) 区间覆盖为 \(k\),有:
矩阵乘法不用动脑子的代价就是常数大吗。——xyh
这样做时间复杂度 \(\mathcal O(q\log n)\),但是矩阵乘法会有 \(5^3\) 的巨大常数,所以下面这份代码得到了 36pts 的高分:
朴素矩阵乘法参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=2.5e5,Q=2.5e5;
typedef unsigned long long ull;
int n,q,a[N+1],b[N+1],preA[N+1],preB[N+1];
ull ans[N+1];
vector<pair<int,int>>f[N+1];
void pre(){vector<int>s;for(int i=1;i<=n;i++){while(s.size()&&a[s.back()]<=a[i]){s.pop_back();}if(s.size()){preA[i]=s.back();}s.push_back(i);}s.resize(0);for(int i=1;i<=n;i++){while(s.size()&&b[s.back()]<=b[i]){s.pop_back();}if(s.size()){preB[i]=s.back();}s.push_back(i);}
}
struct Matrix{int n,m;ull a[5][5];Matrix(int nn=-1,int mm=-1){if(mm==-1){mm=nn;}if(nn!=-1){n=nn,m=mm;}else{n=0,m=0;}memset(a,0,sizeof(a));}Matrix(initializer_list<initializer_list<ull>> x){n=x.size();m=0;for(auto &i:x){m=max(m,(int)i.size());}int pi=0;for(auto &i:x){int pj=0;for(auto &j:i){a[pi][pj]=j;pj++;}pi++;}}ull* operator [](const int &x){return a[x];}void unit(){memset(a,0,sizeof(a));for(int i=0;i<n;i++){a[i][i]=1;}} void print(){for(int i=0;i<n;i++){for(int j=0;j<m;j++){cout<<a[i][j]<<' ';}cout<<endl;}}
};
Matrix operator *(Matrix A,Matrix B){Matrix C(A.n,B.m);for(int i=0;i<A.n;i++){for(int j=0;j<B.m;j++){for(int k=0;k<A.m;k++){C[i][j]+=A[i][k]*B[k][j];}}}return C;
}
Matrix& operator *=(Matrix &A,Matrix B){return A=A*B;
}
Matrix operator +(Matrix A,Matrix B){Matrix C(A.n,A.m);for(int i=0;i<A.n;i++){for(int j=0;j<A.m;j++){C[i][j]=A[i][j]+B[i][j];} } return C;
}
Matrix& operator +=(Matrix &A,Matrix B){for(int i=0;i<A.n;i++){for(int j=0;j<A.m;j++){A[i][j]+=B[i][j];}}return A;
}
struct segTree{struct node{int l,r;Matrix value,tag;}t[N<<2|1];void up(int p){t[p].value=t[p<<1].value+t[p<<1|1].value;}void build(int p,int l,int r){t[p].l=l,t[p].r=r;t[p].value=Matrix(5,1);t[p].tag=Matrix(5);t[p].tag.unit();if(l==r){t[p].value[4][0]=1;return;}int mid=l+r>>1;build(p<<1,l,mid);build(p<<1|1,mid+1,r);up(p);}void down(int p){t[p<<1].value=t[p].tag*t[p<<1].value;t[p<<1].tag=t[p].tag*t[p<<1].tag;t[p<<1|1].value=t[p].tag*t[p<<1|1].value;t[p<<1|1].tag=t[p].tag*t[p<<1|1].tag;t[p].tag.unit(); }void setX(int p,int l,int r,int k){if(l<=t[p].l&&t[p].r<=r){t[p].value=Matrix({{1,0,0,0,0},{0,0,0,k,0},{0,0,0,0,k},{0,0,0,1,0},{0,0,0,0,1}})*t[p].value;t[p].tag=Matrix({{1,0,0,0,0},{0,0,0,k,0},{0,0,0,0,k},{0,0,0,1,0},{0,0,0,0,1}})*t[p].tag;return;}down(p);if(l<=t[p<<1].r){setX(p<<1,l,r,k);}if(t[p<<1|1].l<=r){setX(p<<1|1,l,r,k);}up(p);}void setY(int p,int l,int r,int k){if(l<=t[p].l&&t[p].r<=r){t[p].value=Matrix({{1,0,0,0,0},{0,0,k,0,0},{0,0,1,0,0},{0,0,0,0,k},{0,0,0,0,1}})*t[p].value;t[p].tag=Matrix({{1,0,0,0,0},{0,0,k,0,0},{0,0,1,0,0},{0,0,0,0,k},{0,0,0,0,1}})*t[p].tag;return;}down(p);if(l<=t[p<<1].r){setY(p<<1,l,r,k);}if(t[p<<1|1].l<=r){setY(p<<1|1,l,r,k);}up(p);}void count(){t[1].value=Matrix({{1,1,0,0,0},{0,1,0,0,0},{0,0,1,0,0},{0,0,0,1,0},{0,0,0,0,1}})*t[1].value;t[1].tag=Matrix({{1,1,0,0,0},{0,1,0,0,0},{0,0,1,0,0},{0,0,0,1,0},{0,0,0,0,1}})*t[1].tag;}ull query(int p,int l,int r){if(l<=t[p].l&&t[p].r<=r){return t[p].value[0][0];}down(p);ull ans=0;if(l<=t[p<<1].r){ans+=query(p<<1,l,r);}if(t[p<<1|1].l<=r){ans+=query(p<<1|1,l,r);}return ans;}
}t;
int main(){/*freopen("test.in","r",stdin);freopen("test.out","w",stdout);*/ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int T;cin>>T>>n;for(int i=1;i<=n;i++){cin>>a[i];}for(int i=1;i<=n;i++){cin>>b[i];}cin>>q;for(int i=1;i<=q;i++){int l,r;cin>>l>>r;f[r].push_back({l,i});}pre();t.build(1,1,n);for(int r=1;r<=n;r++){t.setX(1,preA[r]+1,r,a[r]);t.setY(1,preB[r]+1,r,b[r]);t.count();for(auto [l,id]:f[r]){ans[id]=t.query(1,l,r);}}for(int i=1;i<=q;i++){cout<<ans[i]<<'\n';}cout.flush();/*fclose(stdin);fclose(stdout);*/return 0;
}
拆矩阵乘法
那么,如何进一步优化呢?
考虑矩阵乘法维护转移,实际上就是 \(\mathcal O\left(n^2\right)\) 枚举了每一个标记之间的线性转移关系。
但是有一些标记之间毫无关联,例如本题中的 \(d\) 与其余四个标记毫无关系;那么我们再在矩阵乘法里维护它们的转移,就会浪费大量时间,导致 TLE。换而言之,我们可以确定矩阵中的一些项,使其永远为 \(0\)。
因此,从标记间依赖关系来拆矩阵乘法即可。
可以考虑列表,用「\」表示没有依赖关系。下表表示用纵列的标记得到横行的标记的关系。例如第一列第二行表示 \(\textit{hxy}\) 不能更新 \(\displaystyle\sum xy\),即得到 \(\displaystyle\sum xy\) 的转移和 \(\textit{hxy}\) 无关。(其实就是矩阵乘法的顺序。)
观察矩阵转移可得:
| \(\textit{hxy}\) | \(\displaystyle\sum xy\) | \(\displaystyle\sum x\) | \(\displaystyle\sum y\) | \(d\) | |
|---|---|---|---|---|---|
| \(\textit{hxy}\) | |||||
| \(\displaystyle\sum xy\) | \ | ||||
| \(\displaystyle\sum x\) | \ | \ | \ | ||
| \(\displaystyle\sum y\) | \ | \ | \ | ||
| \(d\) | \ | \ | \ | \ |
需要注意依赖关系的依赖。例如 \(d\) 能更新 \(\displaystyle\sum x,\sum y\),最终更新 \(\displaystyle\sum xy\)。
因此等价于可以确定转移矩阵中的一些 \(0\):
一种更聪明的做法
其实可以发现所有的转移矩阵都是主对角线为分割的上三角矩阵。
因此我们仅仅维护上三角即可。
因此可以手动这些非 \(0\) 的维护转移标记:
struct Tag{ull a11,a12,a13,a14,a15,a22,a23,a24,a25,a33,a35,a44,a45,a55;void unit(){a11=a22=a33=a44=a55=1;a12=a13=a14=a15=a23=a24=a25=a35=a45=0;}
};
Tag operator *(Tag a,Tag b){Tag c;c.a11=a.a11*b.a11;c.a12=a.a11*b.a12+a.a12*b.a22;c.a13=a.a11*b.a13+a.a12*b.a23+a.a13*b.a33;c.a14=a.a11*b.a14+a.a12*b.a24+a.a14*b.a44;c.a15=a.a11*b.a15+a.a12*b.a25+a.a13*b.a35+a.a14*b.a45+a.a15*b.a55;c.a22=a.a22*b.a22;c.a23=a.a22*b.a23+a.a23*b.a33;c.a24=a.a22*b.a24+a.a24*b.a44;c.a25=a.a22*b.a25+a.a23*b.a35+a.a24*b.a45+a.a25*b.a55;c.a33=a.a33*b.a33;c.a35=a.a33*b.a35+a.a35*b.a55;c.a44=a.a44*b.a44;c.a45=a.a44*b.a45+a.a45*b.a55;c.a55=a.a55*b.a55;return c;
}
以及线段树节点信息矩阵(向量):
struct Value{ull a1,a2,a3,a4,a5;
};
Value operator *(Tag a,Value b){Value c;c.a1=a.a11*b.a1+a.a12*b.a2+a.a13*b.a3+a.a14*b.a4+a.a15*b.a5;c.a2=a.a22*b.a2+a.a23*b.a3+a.a24*b.a4+a.a25*b.a5;c.a3=a.a33*b.a3+a.a35*b.a5;c.a4=a.a44*b.a4+a.a45*b.a5;c.a5=a.a55*b.a5;return c;
}
时间复杂度
拆完矩阵后,常数从巨大的 \(5^3\) 降低到了 \(14\),可以接受。
实际上还可以将本质不同系数个数从 \(14\) 继续缩减,但是没必要。首先可以发现这里面有一些恒为 \(1\)。
其次,\(d\rightarrow\displaystyle\sum x\) 的贡献其实和 \(\displaystyle\sum y\rightarrow\sum xy\) 的贡献相同。因此可以去除一些,但是没有必要。
AC 代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=2.5e5,Q=2.5e5;
typedef unsigned long long ull;
int n,q,a[N+1],b[N+1],preA[N+1],preB[N+1];
ull ans[N+1];
vector<pair<int,int>>f[N+1];
void pre(){vector<int>s;for(int i=1;i<=n;i++){while(s.size()&&a[s.back()]<=a[i]){s.pop_back();}if(s.size()){preA[i]=s.back();}s.push_back(i);}s.resize(0);for(int i=1;i<=n;i++){while(s.size()&&b[s.back()]<=b[i]){s.pop_back();}if(s.size()){preB[i]=s.back();}s.push_back(i);}
}
struct Value{ull a1,a2,a3,a4,a5;
};
struct Tag{ull a11,a12,a13,a14,a15,a22,a23,a24,a25,a33,a35,a44,a45,a55;void unit(){a11=a22=a33=a44=a55=1;a12=a13=a14=a15=a23=a24=a25=a35=a45=0;}
};
Tag operator *(Tag a,Tag b){Tag c;c.a11=a.a11*b.a11;c.a12=a.a11*b.a12+a.a12*b.a22;c.a13=a.a11*b.a13+a.a12*b.a23+a.a13*b.a33;c.a14=a.a11*b.a14+a.a12*b.a24+a.a14*b.a44;c.a15=a.a11*b.a15+a.a12*b.a25+a.a13*b.a35+a.a14*b.a45+a.a15*b.a55;c.a22=a.a22*b.a22;c.a23=a.a22*b.a23+a.a23*b.a33;c.a24=a.a22*b.a24+a.a24*b.a44;c.a25=a.a22*b.a25+a.a23*b.a35+a.a24*b.a45+a.a25*b.a55;c.a33=a.a33*b.a33;c.a35=a.a33*b.a35+a.a35*b.a55;c.a44=a.a44*b.a44;c.a45=a.a44*b.a45+a.a45*b.a55;c.a55=a.a55*b.a55;return c;
}
Value operator *(Tag a,Value b){Value c;c.a1=a.a11*b.a1+a.a12*b.a2+a.a13*b.a3+a.a14*b.a4+a.a15*b.a5;c.a2=a.a22*b.a2+a.a23*b.a3+a.a24*b.a4+a.a25*b.a5;c.a3=a.a33*b.a3+a.a35*b.a5;c.a4=a.a44*b.a4+a.a45*b.a5;c.a5=a.a55*b.a5;return c;
}
struct segTree{struct node{int l,r;Value value;Tag tag;}t[N<<2|1];void up(int p){t[p].value.a1=t[p<<1].value.a1+t[p<<1|1].value.a1;t[p].value.a2=t[p<<1].value.a2+t[p<<1|1].value.a2;t[p].value.a3=t[p<<1].value.a3+t[p<<1|1].value.a3;t[p].value.a4=t[p<<1].value.a4+t[p<<1|1].value.a4;t[p].value.a5=t[p<<1].value.a5+t[p<<1|1].value.a5;}void build(int p,int l,int r){t[p].l=l,t[p].r=r;t[p].tag.unit();if(l==r){t[p].value.a5=1;return;}int mid=l+r>>1;build(p<<1,l,mid);build(p<<1|1,mid+1,r);up(p);}void down(int p){t[p<<1].value=t[p].tag*t[p<<1].value;t[p<<1].tag=t[p].tag*t[p<<1].tag;t[p<<1|1].value=t[p].tag*t[p<<1|1].value;t[p<<1|1].tag=t[p].tag*t[p<<1|1].tag;t[p].tag.unit(); }void setX(int p,int l,int r,int k){if(l<=t[p].l&&t[p].r<=r){Tag pl;pl.unit();pl.a22=pl.a33=0;pl.a24=pl.a35=k;t[p].value=pl*t[p].value;t[p].tag=pl*t[p].tag;return;}down(p);if(l<=t[p<<1].r){setX(p<<1,l,r,k);}if(t[p<<1|1].l<=r){setX(p<<1|1,l,r,k);}up(p);}void setY(int p,int l,int r,int k){if(l<=t[p].l&&t[p].r<=r){Tag pl;pl.unit();pl.a22=pl.a44=0;pl.a23=pl.a45=k;t[p].value=pl*t[p].value;t[p].tag=pl*t[p].tag;return;}down(p);if(l<=t[p<<1].r){setY(p<<1,l,r,k);}if(t[p<<1|1].l<=r){setY(p<<1|1,l,r,k);}up(p);}void count(){Tag pl;pl.unit();pl.a12=1;t[1].value=pl*t[1].value;t[1].tag=pl*t[1].tag;}ull query(int p,int l,int r){if(l<=t[p].l&&t[p].r<=r){return t[p].value.a1;}down(p);ull ans=0;if(l<=t[p<<1].r){ans+=query(p<<1,l,r);}if(t[p<<1|1].l<=r){ans+=query(p<<1|1,l,r);}return ans;}
}t;
main(){/*freopen("test.in","r",stdin);freopen("test.out","w",stdout);*/ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int T;cin>>T>>n;for(int i=1;i<=n;i++){cin>>a[i];}for(int i=1;i<=n;i++){cin>>b[i];}cin>>q;for(int i=1;i<=q;i++){int l,r;cin>>l>>r;f[r].push_back({l,i});}pre();t.build(1,1,n);for(int r=1;r<=n;r++){t.setX(1,preA[r]+1,r,a[r]);t.setY(1,preB[r]+1,r,b[r]);t.count();for(auto [l,id]:f[r]){ans[id]=t.query(1,l,r);}}for(int i=1;i<=q;i++){cout<<ans[i]<<'\n';}cout.flush();/*fclose(stdin);fclose(stdout);*/return 0;
}
