题目大意:给定长度为 n 的序列: a1,a2,…,an ,记为 a[1:n] 。类似地, a[l:r](1≤l≤r≤N) 是指序列: al,al+1,…,ar−1,ar 。若 1≤l≤s≤t≤r≤n ,则称 a[s:t] 是 a[l:r] 的子序列。现在有 q 个询问,每个询问给定两个数 l 和 r , 1≤l≤r≤n ,求 a[l:r] 的不同子序列的最小值之和。例如,给定序5,2,4,1,3,询问给定的两个数为1和3,那么 a[1:3] 有6个子序列 a[1:1],a[2:2],a[3:3],a[1:2],a[2:3],a[1:3] ,这6个子序列的最小值之和为5+2+4+2+2+2=17。
莫队+单调栈+ST表 时间复杂度 O(nn√)
考虑每个点会成为最小值的区间有什么特点,设当前位置为x,然后用l表示最靠左的大于等于x的位置(也可以说是最靠右的小于x的位置的下一个位置),r表示最靠右的大于等于x的位置。
那么如果区间的最小值是x,那么区间的左端点一定在[l,x],右端点一定在[x,r]
对于每个点的l,r我们可以通过单调栈 O(n) 的求解。
假设现在的询问区间是[ls,rs-1],那么我么考虑加入rs有什么影响?我们实际上是加入了(r-l+1)个区间,现在我们需要知道这些区间的最小值分别是什么。首先我们先确定出[ls,rs]中最小值的位置x,这个的话可以建立ST表,然后每次O(1)的查询。那么对于起点在[ls,x],终点在rs的区间,最小值一定是a[x]这是没问题的,可以O(1)计算。那么对于起点在[x+1,rs]的区间怎么计算呢?其实对于每个点来说控制的一定是一段连续的区间,到现在上面预处理的l,r就可以排上用场了。起点在[rs,r[rs]]的区间的最小值是rs,那么我们可以维护一个类似前缀和的东西。sumr[x]=sumr[r[x]]+(x-r[x])*a[x],那么新增的区间的总值就是sumr[rs]-sumr[mn] mn表示的是区间最小值得位置。
对于l的加入也用类似的方法就可以完美的解决了。
#include
#include
#include
#include
#include
#define N 100003
#define LL long long
using namespace std;
int m,n,st[20][N],l[N],belong[N],top,stack[N],nxt[N],last[N],a[N];
LL ans,c[N],sumr[N],suml[N];
struct data{
int l,r,id;
}q[N];
int cmp(data a,data b){
return belong[a.l]0; stack[++top]=1;
for (int i=2;i<=n;i++) {
while (top&&a[stack[top]]>=a[i]) top--;
last[i]=stack[top]; stack[++top]=i;
}
top=0; stack[0]=n+1; stack[++top]=n; nxt[n]=n+1;
for (int i=n-1;i>=1;i--) {
while (top&&a[stack[top]]>=a[i]) top--;
nxt[i]=stack[top]; stack[++top]=i;
}
for (int i=1;i<=n;i++) sumr[i]=sumr[last[i]]+(LL)(i-last[i])*a[i];
for (int i=n;i>=1;i--) suml[i]=suml[nxt[i]]+(LL)(nxt[i]-i)*a[i];
}
int query(int x,int y)
{
int k=l[y-x];
if (a[st[k][x]]<=a[st[k][y-(1<1]]) return st[k][x];
else return st[k][y-(1<1];
}
void changer(int x,int y,int val)
{
int mn=query(x,y); LL sum=(LL)(mn-x+1)*a[mn];
sum+=sumr[y]-sumr[mn]; ans+=(LL)val*sum;
}
void changel(int x,int y,int val)
{
int mn=query(x,y); LL sum=(LL)(y-mn+1)*a[mn];
sum+=suml[x]-suml[mn]; ans+=(LL)val*sum;
}
int main()
{
freopen("a.in","r",stdin);
// freopen("my.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%d",&a[i]),st[0][i]=i;
for (int i=1;i<=17;i++)
for (int j=1;j<=n;j++)
if (j+(1<1<=n) {
if (a[st[i-1][j]]<=a[st[i-1][j+(1<<(i-1))]]) st[i][j]=st[i-1][j];
else st[i][j]=st[i-1][j+(1<<(i-1))];
}
int j=0;
for (int i=1;i<=n;i++) {
if (1<<(j+1)<=i) j++;
l[i]=j;
}
int block=ceil(sqrt(n));
for (int i=1;i<=n;i++) belong[i]=(i-1)/block+1;
for (int i=1;i<=m;i++) scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
sort(q+1,q+m+1,cmp);
init();
int l=1; int r=1; ans=a[1];
for (int i=1;i<=m;i++) {
while (q[i].r>r) changer(l,++r,1);
while (q[i].l1);
while (q[i].l>l) changel(l++,r,-1);
while (q[i].r1);
c[q[i].id]=ans;
}
for (int i=1;i<=m;i++) printf("%I64d\n",c[i]);
}
线段树 时间复杂度 O(nlogn) 但是常数巨大比上面 O(nn√) 的还要慢。。。。。
我们将询问离线,然后将询问按照终点排序,然后从1到n依次遍历每一个位置,并且同时计算答案。
假设我们现在遍历到的位置为x,对于每个位置我们都维护两个值 sum[now] , val[now] (x后面的点初始值为0).
下面给出 sum 与 val 的定义。 val[now]=min{a[k]} k∈[now,x] 字面意思就是区间[now,x]的最小值。 sum[now]=∑xi=1val[now] 表示的是所有历史版本的 val[now] 的和,换句话说就是起点在now,终点在[now,x]的所有最小值的和。
那么对于每个询问l,r来说,当x=r时 ans=∑ri=lsum[i] 。 好啦现在我们的问题就是如何快速的求 ans .这种区间求知问题容易想到用线段树来维护。
我们现在将每个位置的定义扩展到线段树上,对于每个点now代表[l,r]一区间。
Val[now]=∑ri=lval[now] , Sum[now]=∑xi=1Val[now] ,我们考虑新加入一个位置后,怎么更新前面点的值。
加入a[x],那么对于x最靠左的大于等于x的位置L ,L 到x这段区间的val值都变成了a[x],所有可以用线段树的区间修改Val。对于Sum来说 Sum′=sum+Val 但是如果只是这样做的话,我们的区间标记十分不好维护,所以我们考虑更改修改的方式,建立转移的矩阵,因为矩阵是满足结合律的。
维护四个标记a,b,c,d
Val′=Val∗a+b∗(r−l+1)
Sum′=Sum′+c∗Val′+d∗(r−l+1)
[lenValSum] * ⎡⎣⎢100ba0dc1⎤⎦⎥ 因为区间的长度len是不变的,所以乘上矩阵后的结果其实就是上面的两个式子。
对于两个标记的问题,运用矩阵的结合律就可以了。
⎡⎣⎢100x.bx.a0x.dx.c1⎤⎦⎥ * ⎡⎣⎢100y.by.a0y.dy.c1⎤⎦⎥ = ⎡⎣⎢100y.b+x.b∗y.ax.a∗y.a0y.d+x.d+x.b∗y.cx.c+y.c∗x.a1⎤⎦⎥ 不过对于我们来说其实没必要真正的维护矩阵,只要维护a,b,c,d的值就可以了,每次标记重叠的时候用上面矩阵的计算方式合并即可。初始时所有位置的四个标记的值都是a=1,b=c=d=0.
对于区间Val的修改,我们在对应的区间下放a=0,b=a[x],c=0,d=0.对于全局的Sum的更新我们下放a=1,b=0,c=1,d=0.
#include
#include
#include
#include
#include
#define N 100003
#define LL long long
using namespace std;
struct data{
int l,r,id;
}q[N];
struct node{
LL a,b,c,d;
void clear() { a=1,b=c=d=0;}
}delta[N*4];
int n,m,top,st[N];
LL sum[N*4],val[N*4],a[N],ans[N];
node operator +(node x,node y) { return (node){x.a*y.a,y.b+x.b*y.a,x.c+y.c*x.a,x.d+y.d+x.b*y.c}; }
int cmp(data a,data b){
return a.rint now)
{
val[now]=val[now<<1]+val[now<<1|1];
sum[now]=sum[now<<1]+sum[now<<1|1];
}
void build(int now,int l,int r)
{
val[now]=sum[now]=0; delta[now].clear();
if (l==r) return;
int mid=(l+r)/2;
build(now<<1,l,mid);
build(now<<1|1,mid+1,r);
update(now);
}
void add(int now,int l,int r,node t){
LL len=(LL)(r-l+1);
sum[now]+=t.c*val[now]+t.d*len;
val[now]=t.a*val[now]+t.b*len;
delta[now]=delta[now]+t;
}
void pushdown(int now,int l,int r)
{
int mid=(l+r)/2; node t=delta[now];
if (t.a!=1||t.b||t.c||t.d) {
add(now<<1,l,mid,t); add(now<<1|1,mid+1,r,t);
delta[now].clear();
}
}
void qjchange(int now,int l,int r,int ll,int rr,node t)
{
if (ll<=l&&r<=rr) {
add(now,l,r,t);
return;
}
int mid=(l+r)/2;
pushdown(now,l,r);
if (ll<=mid) qjchange(now<<1,l,mid,ll,rr,t);
if (rr>mid) qjchange(now<<1|1,mid+1,r,ll,rr,t);
update(now);
}
LL qjsum(int now,int l,int r,int ll,int rr)
{
if (ll<=l&&r<=rr) return sum[now];
int mid=(l+r)/2; pushdown(now,l,r);
LL ans=0;
if (ll<=mid) ans+=qjsum(now<<1,l,mid,ll,rr);
if (rr>mid) ans+=qjsum(now<<1|1,mid+1,r,ll,rr);
return ans;
}
int main()
{
freopen("a.in","r",stdin);
// freopen("my.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%I64d",&a[i]);
for (int i=1;i<=m;i++) scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
sort(q+1,q+m+1,cmp);
top=0; build(1,1,n); int j=1;
for (int i=1;i<=n;i++) {
while (top&&a[st[top]]>=a[i]) top--;
node tmp=(node){0,a[i],0,0};
qjchange(1,1,n,st[top]+1,i,tmp);
tmp=(node){1,0,1,0}; st[++top]=i;
add(1,1,n,tmp);
while (j<=m&&q[j].r==i) ans[q[j].id]=qjsum(1,1,n,q[j].l,q[j].r),j++;
}
for (int i=1;i<=m;i++) printf("%I64d\n",ans[i]);
}