ST表

解决区间可以重复贡献的问题,比如,区间最小值,区间最大值,区间gcd,多次计算不影响结果,但是区间求和就不行

eg:给你一个序列Hi,和m次询问(si,ti),要求你从(si,Hsi)-->(ti,Hti)只能向9个方向走(3*3),并且要求到达i位置纵位置不能<Hi,相当于是跨山峰

发现无论中间经过的山峰有多复杂,我只要满足“最高的”山峰可以跨过去就行,那我用hi代表最高山峰,hi-si+hi-ti就是需要斜着走的步数?不是,因为可能会有山阻挡,所以要分别算,从si到maxhi预先向上走的步数,从maxhi到ti预先向下走的步数,贪心:

向上走,一步也不多走,走maxh-hsi;下也是;平走,是区间距离-斜走,但是斜走的只有上走-预留的(预留的必须走),不算里面

最后你发现算预留就是(上)max[hi-hsi-(i-si)],所以st表处理出来

int f[N][21],f1[N][21],f2[N][21];
int n,Q,l2[N];
int main()
{
   //freopen("qiandao4.in","r",stdin);
   // freopen("dfs.txt","w",stdout);
    n=re(); l2[1]=0;l2[2]=1;
    _f(i,1,n)
    {
        f[i][0]=re();
        f1[i][0]=f[i][0]-i;
        f2[i][0]=f[i][0]+i;
       // chu("f")
    }
    _f(i,3,n)l2[i]=l2[i>>1]+1;
    Q=re();
    _f(i,1,20)
    {
        _f(j,1,n)
        {
            if(j+(1<<i)-1 >n)break;
            f[j][i]=max(f[j][i-1],f[j+(1<<(i-1))][i-1]);
            f1[j][i]=max(f1[j][i-1],f1[j+(1<<(i-1))][i-1]);
            f2[j][i]=max(f2[j][i-1],f2[j+(1<<(i-1))][i-1]);
        }
    }
    _f(i,1,Q)
    {
        int si=re(),ti=re();int len=l2[ti-si+1];ll ans=0;
        if(si<ti)
        {
             int len=l2[ti-si+1];
            int max_h=max(f[si][len],f[ti-(1<<len)+1][len]);
            int max_s=max(f1[si][len],f1[ti-(1<<len)+1][len])-f1[si][0];
            int max_t_=max(f2[si][len],f2[ti-(1<<len)+1][len])-f2[ti][0];
           // chu("\n%d+%d:%d  %d+%d:%d\n",si,len,f2[si][len],ti-(1<<len)+1,len,f2[ti-(1<<len)+1][len]);
            int cnt=ti-si-(max_h-f[si][0]-max_s)-(max_h-f[ti][0]-max_t_);
          //  chu("预留上升的:%d,预留下降的:%d,自己最高的:%d,平走:%d\n",max_s,max_t_,max_h,cnt);
             ans=(ll)(max_h-f[si][0])*4+(ll)(max_h-f[ti][0])+(ll)cnt*2;
        }
        else 
        {
            swap(si,ti);
            int len=l2[ti-si+1];
            int max_h=max(f[si][len],f[ti-(1<<len)+1][len]);
            int max_s=max(f1[si][len],f1[ti-(1<<len)+1][len])-f1[si][0];
            int max_t_=max(f2[si][len],f2[ti-(1<<len)+1][len])-f2[ti][0];
            int cnt=ti-si-(max_h-f[si][0]-max_s)-(max_h-f[ti][0]-max_t_);
             ans=(ll)(max_h-f[si][0])+(ll)(max_h-f[ti][0])*4+(ll)cnt*2;
        }
        chu("%lld\n",ans);
    }
    return 0;
}

给你一个序列,多次询问,求给定区间每个子区间的最小值的和值

多个询问[L,R]考虑离线分块莫队,对于每次在整个区间多出来的1个数,考虑多出来的贡献,每个数,假如当前区间是L,R,向右拓展一个位置pos[R+1],那么这个数的产生在L,R的贡献就是分为2个部分(1)第一个部分就是找到L,R+1区间的min,这部分的贡献就是val[min](pos_min-L+1),对应的是左端点,右端点就是R+1(2)第二个就是pos_min+1--R+1的区间的贡献,不一定来自val[R+1],比如2 3 0 4 3 6,最小值都不在6出现,但是可以预处理sum_R[i],单个表示i位置在前面最多产生的贡献,就是val[i](i-L[i]+1),sum_R[i]=\(\sum_{i=1}^{i}\)val[i]*(i-L[i]+1) sum_R[R+1]-sum_R[pos]就是pos+1~R+1的贡献,为什么不直接sum[R+1]-sum[L-1]?因为R+1不一定能延伸到L的位置,所以会算上多余的不属于R+1的贡献,min相当于找到了那个连续的继承(前缀和是从上一个L【i】-1转移)点,所以就转移过来了


int n,Q;
int a[maxn];
int q[maxn];
int pre[maxn],nxt[maxn];
ll f[maxn],g[maxn];
int pos[maxn][22];
int get(int x,int y)
{
    if(a[x]<a[y])  return x;
    return y;
}
int lg[maxn];
struct P{
    int l,r,id,bl;
}b[maxn];
bool cmp(P a,P b)
{
    if(a.bl==b.bl)  return a.r<b.r;
    return a.bl<b.bl;
}
ll rig(int r,int l)
{
    int len=r-l+1;
    int po=get(pos[l][lg[len]],pos[r-(1<<(lg[len]))+1][lg[len]]);
    ll tmp=0;
    tmp=(ll)(po-l+1)*a[po];
    tmp+=f[r]-f[po];
    return tmp;
}
ll lef(int l,int r)
{
    int len=(r-l+1);
    int po=get(pos[l][lg[len]],pos[r-(1<<(lg[len]))+1][lg[len]]);
    ll tmp=0;
    tmp=(ll)(r-po+1)*a[po];
    tmp+=g[l]-g[po];
    return tmp;
}
ll ans[maxn];
bool cmpx(P a,P b)
{
    return a.id<b.id;
}
int main()
{
    n=read();Q=read();
    for(int i=1;i<=n;i++)  a[i]=read();
    a[n+1]=-inf;
    int l=1,r=0;
    for(int i=1;i<=n;i++)
    {
        while(l<=r&&a[q[r]]>=a[i])  
		{
			nxt[q[r]]=i;
			r--;
		}
        pre[i]=q[r];
        q[++r]=i;
    }
	while(l<=r&&a[q[r]]>=a[n+1])
	{
		nxt[q[r]]=n+1;r--;
	}
    for(int i=1;i<=n;i++)  f[i]=f[pre[i]]+(ll)(i-pre[i])*(ll)a[i];
    for(int i=n;i>=1;i--)  g[i]=g[nxt[i]]+(ll)(nxt[i]-i)*(ll)a[i];
    for(int i=1;i<=n;i++)  pos[i][0]=i;
    for(int j=1;j<=20;j++)
      for(int i=1;i<=n;i++)  pos[i][j]=get(pos[i][j-1],pos[i+(1<<(j-1))][j-1]);
	  lg[1]=0,lg[2]=1;
    for(int i=3;i<=n;i++)  lg[i]=lg[i>>1]+1;
    int block=sqrt(n);
    for(int i=1;i<=Q;i++)
    {
        b[i].l=read();b[i].r=read();b[i].id=i;
        b[i].bl=(b[i].l-1)/block+1;
    }
    sort(b+1,b+Q+1,cmp);
    int L=1,R=0;
    ll now=0;
    for(int i=1;i<=Q;i++)
    {
        while(R<b[i].r)
        {
            R++;
            now+=rig(R,L);
        }
        while(R>b[i].r)
        {
            now-=rig(R,L);
            R--;
        }
        while(L<b[i].l)
        {
            now-=lef(L,R);
            L++;
        }
        while(L>b[i].l)
        {
            L--;
            now+=lef(L,R);
        }
        ans[b[i].id]=now;
    }
    for(int i=1;i<=Q;i++)   printf("%lld\n",ans[i]);
    return 0;
}
posted on 2022-08-08 20:42  HZOI-曹蓉  阅读(22)  评论(0编辑  收藏  举报