P3246 [HNOI2016] 序列
P3246 [HNOI2016] 序列
Solution
可以将一个区间 \([l,r]\) 映射为平面上的一个点 $(l,r) $ ,令其权值为区间最小值,那么每次询问 \([l,r]\) 的答案就是求一个以 $(l,l) $ 为右上角以 \((r,r)\) 为右下角的矩阵的点权和
但是这里点权明显不可以一个个赋,所以考虑差分
用单调栈可以找出 \(a_i\) 左边第一个小于或等于 \(a_i\) 的 \(a_l\) ,同理可以找出右边第一个小于 \(a_i\) 的 \(a_r\) ,那么左上角 \((l+1,i)\) 右下角 \((i,r-1)\) 的
矩阵里每一点的点权都为 \(a_i\) ,这个是很方便用差分实现的,这样就把单点赋权转化为了区间修改
设 \(cf\) 为差分数组, \(b\) 为由差分还原后得到的平面, \(sum\) 为 \(b\) 的前缀和
那么每次询问的 \(ans = sum[r][r]+sum[l-1][l-1]-sum[l-1][r]-sum[r][l-1]\)
这样子就把一次询问拆解成了四次单点查询
考虑 \((i,j)\) 一点上的差分对 \(sum\) 的影响,可以发现 \((i,j)\) 上的差分会对其右下角任意一点 \((x,y)\) 贡献 \((x-i+1)(y-i+1)\) 次,即有
\[(x-i+1)(y-j+1){\times}cf[i][j] = cf[i][j]\times[(x+1)(y+1)-i(y+1)-j(x+1)+ij]
\]
的贡献
那么就有
\[\begin{aligned}
sum[x][y] &= \sum\limits_{i=1}^{x}\sum\limits_{j=1}^{y}cf[i][j]\times[(x+1)(y+1)-i(y+1)-j(x+1)+ij]\\
\end{aligned}
\]
进一步地 可以把 \(sum[x][y]\) 拆成四项之和的形式,四项分别为
\[\begin{aligned}
(x+1)(y+1)&\sum_{i=1}^{x}\sum_{j=1}^{y}cf[i][j]\\
&\sum_{i=1}^{x}\sum_{j=1}^{y}cf[i][j]\times ij\\
-(y+1)&\sum_{i=1}^{x}\sum_{j=1}^{y}cf[i][j]\times i\\
-(x+1)&\sum_{i=1}^{x}\sum_{j=1}^{y}cf[i][j]\times j
\end{aligned}
\]
于是可以把原题离线下来做,开四个树状数组分别维护式子中形如 \(\sum_{i=1}^{x}\sum_{j=1}^{y}cf[i][j]{\times}z\) 的那一部分
Code
#include <bits/stdc++.h>
#define rg register
#define il inline
#define int ll
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using namespace std;
namespace myspace{
const char endl = '\n';
const int maxn = 1e5+50;
il int read(){
int x=0,f=1;
char c = getchar();
while(c<'0' || c>'9'){
if(c == '-') f = -1;
c = getchar();
}
while(c>='0' && c<='9'){
x = x*10+c-'0';
c = getchar();
}
return x*f;
}
il void write(int x){
if(x<0){
putchar('-');
write(-x);
} else if(x<10){
putchar('0'+x);
} else{
write(x/10);
putchar('0'+x%10);
}
}
il void writeln(int x){
write(x);
putchar('\n');
}
int n,q;
int a[maxn],l[maxn],r[maxn];
int stk[maxn],top;//这个是单调栈
il int lb(int x){
return x&-x;
}
struct BIT{
int tr[maxn];
il int qry(int x){
int res = 0;
while(x){
res += tr[x];
x -= lb(x);
}
return res;
}
il void add(int x,int y){
while(x<=n){
tr[x] += y;
x += lb(x);
}
}
} tr1,tr2,tr3,tr4;
struct action{
int id;//操作id,对于一次查询操作即为询问id,修改操作统一为0
int x,y;//操作点(x,y)的坐标
int w;//操作数,即询问的系数或修改的差分值
const friend bool operator<(const action & a,const action & b){
if(a.y == b.y){
return a.id<b.id;//让修改操作先于查询操作
}
return a.y<b.y;
}
};
vector<action> act[maxn];
int ans[maxn];
signed main(){
n = read();
q = read();
for(int i = 1;i<=n;i++){
a[i] = read();
}
stk[0] = 0;
top = 0;
for(int i = 1;i<=n;i++){
while(top && a[i]<a[stk[top]]){
top--;
}
l[i] = stk[top];
stk[++top] = i;
}
stk[0] = n+1;
top = 0;
for(int i = n;i>=1;i--){
while(top && a[i]<=a[stk[top]]){
top--;
}
r[i] = stk[top];
stk[++top] = i;
}
for(int i = 1;i<=n;i++){
int L = l[i];
int R = r[i];
act[L+1].push_back({0,L+1,i,a[i]});
act[L+1].push_back({0,L+1,R,-a[i]});
act[i+1].push_back({0,i+1,i,-a[i]});
act[i+1].push_back({0,i+1,R,a[i]});
}//操作点:修改
for(int i = 1;i<=q;i++){
int L = read(),R = read();
act[R].push_back({i,R,R,1});
act[L-1].push_back({i,L-1,R,-1});
act[R].push_back({i,R,L-1,-1});
act[L-1].push_back({i,L-1,L-1,1});
}//操作点:查询
for(int i = 1;i<=n;i++){
for(auto[id,x,y,w] : act[i]){
if(id == 0){
tr1.add(y,w);
tr2.add(y,w*x*y);
tr3.add(y,w*x);
tr4.add(y,w*y);
} else{
ans[id] += w*(x+1)*(y+1)*tr1.qry(y);
ans[id] += w*tr2.qry(y);
ans[id] += -w*(y+1)*tr3.qry(y);
ans[id] += -w*(x+1)*tr4.qry(y);
}
}
}
for(int i = 1;i<=q;i++){
writeln(ans[i]);
}
return 0;
}
}
signed main(){
#ifdef ONLINE_JUDGE
#else
freopen("in.in","r",stdin);
freopen("out.out","w",stdout);
#endif
myspace::main();
return 0;
}

浙公网安备 33010602011771号