当前位置: 首页 > news >正文

题解:[NOIP2022] 比赛

题目传送门

题意分析

原题面等价于给定序列 \(a_1,a_2,a_3,\cdots,a_n\)\(b_1,b_2,b_3,\cdots,b_n\)\(q\) 次询问,每次询问给定 \(l,r\),求:

\[\sum_{p=l}^r\sum_{q=p}^r\max_{i=p}^qa_i\max_{i=p}^qb_i\bmod2^{64} \]

ST 表暴力做是 \(\mathcal O\left(qn^2\right)\) 的,可以得到 8pts 的高分。

设:

\[\begin{aligned} x_{l,r}&=\max_{i=l}^ra_i\\ y_{l,r}&=\max_{i=l}^rb_i \end{aligned} \]

则,答案就是:

\[\sum_{p=l}^r\sum_{q=l}^rx_{p,q}y_{p,q} \]

考虑对 \(r\) 进行扫描线,可以得到转移:

\[\begin{aligned} x_{l,r}&=\max(x_{l,r-1},a_r)\\ y_{l,r}&=\max(y_{l,r-1},b_r)\\ \end{aligned} \]

对于询问 \((l_0,r_0)\),答案为:

\[\sum_{r=l_0}^{r_0}\sum_{l=l_0}^rx_{l,r}y_{l,r} \]

因此我们在扫描 \(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\) 都为定值。

\[\begin{bmatrix} \textit{hxy}\\ \displaystyle\sum xy\\ \displaystyle\sum x\\ \displaystyle\sum y\\ d \end{bmatrix} \]

对于求历史和,有转移矩阵:

\[\begin{bmatrix} 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 \end{bmatrix} \begin{bmatrix} \textit{hxy}\\ \displaystyle\sum xy\\ \displaystyle\sum x\\ \displaystyle\sum y\\ d \end{bmatrix} = \begin{bmatrix} \displaystyle \textit{hxy}+\sum xy\\ \displaystyle\sum xy\\ \displaystyle\sum x\\ \displaystyle\sum y\\ d \end{bmatrix} \]

之后考虑 \(x\) 区间覆盖为 \(k\),有:

\[\begin{bmatrix} 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 \end{bmatrix} \begin{bmatrix} \textit{hxy}\\ \displaystyle\sum xy\\ \displaystyle\sum x\\ \displaystyle\sum y\\ d \end{bmatrix} = \begin{bmatrix} \displaystyle \textit{hxy}\\ \displaystyle k\sum y\\ dk\\ \displaystyle\sum y\\ d \end{bmatrix} \]

同理还有 \(y\) 区间覆盖为 \(k\),有:

\[\begin{bmatrix} 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 \end{bmatrix} \begin{bmatrix} \textit{hxy}\\ \displaystyle\sum xy\\ \displaystyle\sum x\\ \displaystyle\sum y\\ d \end{bmatrix} = \begin{bmatrix} \displaystyle \textit{hxy}\\ \displaystyle k\sum x\\ \displaystyle\sum x\\ dk\\ d \end{bmatrix} \]

矩阵乘法不用动脑子的代价就是常数大吗。——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\)

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

一种更聪明的做法

其实可以发现所有的转移矩阵都是主对角线为分割的上三角矩阵。

因此我们仅仅维护上三角即可。

因此可以手动这些非 \(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;
}
http://www.jsqmd.com/news/871327/

相关文章:

  • 手机怎样拍出规范证件照?2026手机拍证件照方法详解与工具盘点 - 软件小管家
  • 基于全志T153多核异构架构的工业PLC主控方案设计与实战
  • 制作亿贝网页
  • 沧州卖金亲历:跑了好几家,最后只认福正美 - 上门黄金回收
  • 基于Intel Core处理器的高性能嵌入式系统定制开发实战指南
  • arXiv-sanity-preserver:如何从海量学术论文中精准找到你需要的AI研究?
  • 世纪联华超市卡回收变现策略 - 购物卡回收找京尔回收
  • 通过Node.js后端服务快速集成Taotoken实现多轮对话功能
  • 2026年最新AI写作辅助软件全攻略(含新手入门指南)
  • 分期乐京东e卡回收全流程教学,操作简单易上手 - 京顺回收
  • 想让天虹购物卡回收更划算?回收技巧揭秘 - 团团收购物卡回收
  • 居家维修不用愁!维小达全品类上门服务,便民又省心 - 维小达科技
  • RT-Thread浮点打印异常解决方案:从newlib-nano到内存对齐
  • 车载软件vECU虚拟化测试:原理、实践与工具链全解析
  • ADAS系统架构与核心功能实现:从传感器融合到整车集成实战
  • 2026年4月热门的工业厂房搭建服务商口碑推荐,节能照明方案,降低厂房能耗成本 - 品牌推荐师
  • 2026 宁波代理记账优质机构盘点推荐|本地靠谱财税托管服务商甄选指南 - 品牌智鉴榜
  • 2026年宜昌黄金回收 普通人避坑指南 五大合规机构安全交易 - 黄金回收
  • 手机证件照怎么生成?2026实测生成方法+软件推荐 - 软件小管家
  • 零售Agent不是“聊天机器人”!用37项NLU/NLG基准测试数据,重定义真正的自主决策Agent
  • Feishin:打造你的终极私人音乐世界完整指南
  • 渗透测试的信息收集???
  • 在Node点js服务中集成Taotoken并调用多个大模型
  • 实战突破:深度掌握PySC2星际争霸II AI开发环境搭建与配置
  • 盘点永辉超市购物卡回收平台:谁更值得信赖? - 团团收购物卡回收
  • WSL+ROS 2 (Humble) 安装与话题测试 (Ubuntu 22.04)
  • OpCore Simplify:简化OpenCore EFI配置的完整指南
  • GPU加速多波束相控阵雷达:从并行计算原理到实时系统实现
  • 实时光线追踪:从渲染到设计建模的核心技术与应用
  • 电流检测放大器(CSA)如何解决高精度电流采样难题