• bzoj4540 [Hnoi2016]序列


    4540: [Hnoi2016]序列

    Time Limit: 20 Sec  Memory Limit: 512 MB
    Submit: 1758  Solved: 834
    [Submit][Status][Discuss]

    Description

      给定长度为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。

    Input

      输入文件的第一行包含两个整数n和q,分别代表序列长度和询问数。接下来一行,包含n个整数,以空格隔开
    ,第i个整数为ai,即序列第i个元素的值。接下来q行,每行包含两个整数l和r,代表一次询问。

    Output

      对于每次询问,输出一行,代表询问的答案。

    Sample Input

    5 5
    5 2 4 1 3
    1 5
    1 3
    2 4
    3 5
    2 5

    Sample Output

    28
    17
    11
    11
    17

    HINT

    1 ≤N,Q ≤ 100000,|Ai| ≤ 10^9

    分析:非常好的一道题.

       先看看这道题有什么特点:只有查询操作,查询操作带有浓浓的数学味道.

       关于多次查询问题,最简单的做法就是先预处理,把所有可能的答案用数组存下来. 这道题中l,r可能高达10w,存不下.那就省掉一维,试着维护前缀和?似乎可以,但是好像不太好推出一个区间[l,r]的答案.

       只有查询,没有修改,可以想到一个经典算法:莫队算法. 那么现在的问题就变成了从[L,R-1]转移到[L,R],答案会发生什么变化.(其它的转移可以类比).

       显然只需要考虑以R为右端点,左端点在[L,R]的区间的贡献. 因为有影响的是当前区间的最小值,所以先找到[L,R]的最小值的位置pos. 对于左端点在[L,pos]的区间,答案肯定是a[pos]. 那么左端点在[pos + 1,R]的区间的答案是什么呢? 

       不太容易想到. 维护一个前缀和. 令fl[i]表示以i为右端点,左端点在[1,i]的区间的答案之和,l[i]表示i左边第一个大于i的数的位置. 那么fl[i] = fl[l[i]] + a[l] * (i - l[i]). l[i]可以用单调栈来处理.  左端点在[pos + 1,R]的区间的答案和就是fl[R] - fl[pos].因为a[pos] <= a[R],如果左端点跨过了pos,一定会被fl[pos]统计到,所以满足区间相减的性质.

       剩下的几种操作也是类似的. 10w的数据,只有60分.

       怎么办呢? 这种多次查询的问题要么每次能够很快地回答查询,通常利用数据结构,要么利用维护的前缀/后缀信息来计算得到答案. 考虑第二种方法.

       一个区间可以理解为一个左端点和右端点组成的二元组(l,r),对二元组分类来求. 

       还是先找到[l,r]的最小值的位置pos. 将[l,r]内的区间分成3类:

       1.左端点右端点分别在pos左右的.

       2.左端点右端点都在pos左侧的.

       3.左端点右端点都在pos右侧的.

       对于第一类区间的答案,由于a[pos]是[l,r]的最小值了,那么这一类区间的答案之和就是a[pos] * (pos - l + 1) * (r - pos + 1).

       另外两类本质上是一样的,弄清楚一类怎么统计就好了.

       考虑第3类. 先对fl数组求个前缀和suml, 因为现在是直接统计答案而不是用  莫队的增量法只考虑一个端点的影响了. 那么右端点在pos右侧的答案之和就是suml[r] - suml[pos]. 这保证了右端点是合法的,但是左端点可能在pos左边,怎么办呢?

       和莫队算法的思想是一样的. 因为a[pos]是最小的. 那么跨过pos的区间的答案一定是a[pos].现在只需要减去左端点在pos左侧的答案即可,它们的答案是fl[pos] * (r - pos).  如果把fl[pos]看作a[pos] * pos在左边能控制的点的范围(权值比a[pos]大的点). 那么就相当于合法的点对数*贡献,有点乘法原理的意思.

       如何查询最小值的位置?要实现O(1)查询最值,ST表即可. 注意,这里的ST表不再是记录值,而是记录下标.

       so,这道题就被完美的解决了,最终的时间复杂度是O(nlogn).  

       回顾一下这道题.

       先知道这个问题的特征是什么:没有修改操作,只有多次查询操作.

       多次查询问题怎么办?要么每次查询的足够快,要么直接预处理所有可能的区间的答案,要么维护前缀/后缀信息能快速查询.

       查询的足够快?上数据结构!事实上这道题确实有线段树的做法,只是我不会......

       预处理所有可能的区间的答案?数组开不下,显然不可以.

       维护前缀/后缀信息快速查询答案?有点难想......

       但是别忘了,解决这类题目有一个算法--莫队算法. 利用增量法,固定一个端点,另一个端点在一个区间内的答案和. 10w的数据会T.

       那么就直接求区间的答案就好了. 将区间看作一个二元组,进行分类,分别用维护的信息来求就好啦. 

       总的思路就是这样的,还是很清晰的.

    60分莫队:

    #include <cstdio>
    #include <cmath>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    typedef long long ll;
    const ll maxn = 100010,inf = 0x7fffffff;
    ll n,Q,top,sta[maxn],a[maxn],l[maxn],r[maxn],fl[maxn],fr[maxn],block;
    ll pos[maxn][20],L = 1,R = 0,anss[maxn],ans;
    
    struct node
    {
        ll l,r,id;
    }q[maxn];
    
    bool cmp(node a,node b)
    {
        ll alpos = (a.l - 1) / block + 1,blpos = (b.l - 1) / block + 1;
        if (alpos == blpos)
            return a.r < b.r;
        return alpos < blpos;
    }
    
    void pre()
    {
        top = 0;
        for (ll i = 1; i <= n; i++)
        {
            while(top && a[sta[top]] > a[i])
                top--;
            if (!top)
                l[i] = 0;
            else
                l[i] = sta[top];
            sta[++top] = i;
        }
        memset(sta,0,sizeof(sta));
        top = 0;
        for (ll i = n; i >= 1; i--)
        {
            while(top && a[sta[top]] > a[i])
                top--;
            if (!top)
                r[i] = n + 1;
            else
                r[i] = sta[top];
            sta[++top] = i;
        }
        fl[1] = a[1];
        for (ll i = 2; i <= n; i++)
            fl[i] = fl[l[i]] + (i - l[i]) * a[i];
        fr[n] = a[n];
        for (ll i = n - 1; i >= 1; i--)
            fr[i] = fr[r[i]] + (r[i] - i) * a[i];
        for (ll i = 1; i <= n; i++)
            pos[i][0] = i;
        for (ll j = 1; j <= 19; j++)
            for (ll i = 1; i + (1 << j) - 1 <= n; i++)
            {
                if (a[pos[i][j - 1]] < a[pos[i + (1 << (j - 1))][j - 1]])
                    pos[i][j] = pos[i][j - 1];
                else
                    pos[i][j] = pos[i + (1 << (j - 1))][j - 1];
            }
    }
    
    ll query(ll l,ll r)
    {
        ll t = (ll)((log(r - l + 1)) / log(2.0));
        if (a[pos[l][t]] < a[pos[r - (1 << t) + 1][t]])
            return pos[l][t];
        return pos[r - (1 << t) + 1][t];
    }
    
    void add1(ll l,ll r,ll v)
    {
        if (l > r)
            return;
        ll temp = query(l,r);
        ans += v * (temp - l + 1) * a[temp];
        ans += v * (fl[r] - fl[temp]);
    }
    
    void add2(ll l,ll r,ll v)
    {
        if (l > r)
            return;
        ll temp = query(l,r);
        ans += v * (r - temp + 1) * a[temp];
        ans += v * (fr[l] - fr[temp]);
    }
    
    void solve()
    {
        for (ll i = 1; i <= Q; i++)
        {
            ll l = q[i].l,r = q[i].r;
            while (R < r)
            {
                ++R;
                add1(L,R,1);
            }
            while(R > r)
            {
                add1(L,R,-1);
                R--;
            }
            while (L < l)
            {
                add2(L,R,-1);
                L++;
            }
            while (L > l)
            {
                L--;
                add2(L,R,1);
            }
            anss[q[i].id] = ans;
        }
    }
    
    int main()
    {
        scanf("%lld%lld",&n,&Q);
        block = sqrt(n);
        for (ll i = 1; i <= n; i++)
            scanf("%lld",&a[i]);
        pre();
        for (ll i = 1; i <= Q; i++)
        {
            scanf("%lld%lld",&q[i].l,&q[i].r);
            q[i].id = i;
        }
        sort(q + 1,q + 1 + Q,cmp);
        solve();
        for (ll i = 1; i <= Q; i++)
            printf("%lld
    ",anss[i]);
    
        return 0;
    }

    100分算法:

    #include <cstdio>
    #include <cmath>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    typedef long long ll;
    const ll maxn = 100010,inf = 0x7fffffff;
    ll n,Q,top,sta[maxn],a[maxn],l[maxn],r[maxn],fl[maxn],fr[maxn],block;
    ll pos[maxn][20],L = 1,R = 0,anss[maxn],ans,suml[maxn],sumr[maxn];
    
    struct node
    {
        ll l,r,id;
    }q[maxn];
    
    bool cmp(node a,node b)
    {
        ll alpos = (a.l - 1) / block + 1,blpos = (b.l - 1) / block + 1;
        if (alpos == blpos)
            return a.r < b.r;
        return alpos < blpos;
    }
    
    void pre()
    {
        top = 0;
        for (ll i = 1; i <= n; i++)
        {
            while(top && a[sta[top]] > a[i])
                top--;
            if (!top)
                l[i] = 0;
            else
                l[i] = sta[top];
            sta[++top] = i;
        }
        memset(sta,0,sizeof(sta));
        top = 0;
        for (ll i = n; i >= 1; i--)
        {
            while(top && a[sta[top]] > a[i])
                top--;
            if (!top)
                r[i] = n + 1;
            else
                r[i] = sta[top];
            sta[++top] = i;
        }
        fl[1] = a[1];
        for (ll i = 2; i <= n; i++)
            fl[i] = fl[l[i]] + (i - l[i]) * a[i];
        fr[n] = a[n];
        for (ll i = n - 1; i >= 1; i--)
            fr[i] = fr[r[i]] + (r[i] - i) * a[i];
        for (ll i = 1; i <= n; i++)
            suml[i] = suml[i - 1] + fl[i];
        for (ll i = n; i >= 1; i--)
            sumr[i] = sumr[i + 1] + fr[i];
        for (ll i = 1; i <= n; i++)
            pos[i][0] = i;
        for (ll j = 1; j <= 19; j++)
            for (ll i = 1; i + (1 << j) - 1 <= n; i++)
            {
                if (a[pos[i][j - 1]] < a[pos[i + (1 << (j - 1))][j - 1]])
                    pos[i][j] = pos[i][j - 1];
                else
                    pos[i][j] = pos[i + (1 << (j - 1))][j - 1];
            }
    }
    
    ll query(ll l,ll r)
    {
        ll t = (ll)((log(r - l + 1)) / log(2.0));
        if (a[pos[l][t]] < a[pos[r - (1 << t) + 1][t]])
            return pos[l][t];
        return pos[r - (1 << t) + 1][t];
    }
    
    int main()
    {
        scanf("%lld%lld",&n,&Q);
        block = sqrt(n);
        for (ll i = 1; i <= n; i++)
            scanf("%lld",&a[i]);
        pre();
        for (ll i = 1; i <= Q; i++)
        {
            ll l,r;
            scanf("%lld%lld",&l,&r);
            ll temp = query(l,r);
            ans = 0;
            ans += a[temp] * (temp - l + 1) * (r - temp + 1);
            ans += suml[r] - suml[temp] - fl[temp] * (r - temp);
            ans += sumr[l] - sumr[temp] - fr[temp] * (temp - l);
            printf("%lld
    ",ans);
        }
    
        return 0;
    }
  • 相关阅读:
    我从Linux走来,选择了Windows
    静态路由(三路由)
    设置跨交换机VLAN
    服务器只能被动接受
    理解 atime,ctime,mtime (下)
    理解 atime,ctime,mtime (上)
    visualvm连接服务器jvm进行监控
    mysql 日期函数格式
    关于springmvc跨域
    Javassist初体验
  • 原文地址:https://www.cnblogs.com/zbtrs/p/8576430.html
Copyright © 2020-2023  润新知