zoukankan      html  css  js  c++  java
  • 2019.9.7 与众不同

    题目这里

     

    大概就是找到每个区间里最长的序列使得其中没有重复

    思路:

    因为知道可以用ST表维护但不是简单的最大值,所以第一个思路是

    用dp[i][j]表示正常的ST表,但是其值表示以点i结尾的最大完美序列长度

    为什么能想到这个思路?原因非常简单,我们已经知道ST表正常情况下

    只能用来维护最大最小值,所以我们必须把序列长度往最大最小这个思路里靠,

    就有了存储以i结尾序列长度最大值的存储方法

    但是我们会发现有这样一个问题:

    这个图里的竖线表示黑色区间里以i结尾的所有完美序列中最长的序列的那个i,

    我们记这个点是p

    (也就是这段区间所有内以其结尾的最长完美序列的结尾点)

    但是如果橙色区间内是点p结尾的最长完美序列覆盖到的地方,

    我们可以发现它已经超过了待求区间的范围

    怎么办???


    解决问题首先要明确问题的范围

    所以我们用f[x]表示在整个数组中x之前出现的上一个与其值相同的点得位置

    比如说 点x对应的值是5 f[x]就是在x之前上一个5的位置

    这样我们就明确了什么时候会出现第一部分的情况

    从f数组的思路 我们可以得到一个last数组 last[x]表示以x结尾的最长完美序列的起点位置

    这样我们就可以用st[i]表示以i结尾的最长完美序列的长度

    很明显st[i]=i-last[i]+1

    所以初始化这两个数组(f数组在代码中不需要)只需要初始化last

    为了初始化last 我们借助尺取法的思路

    一开始使用两个指针l,r都指向位置n,再开一个book数组记录已经出现过哪些数

    注意book一定要开map 因为原序列有负数!!!!!!(哭唧唧qwq)

    所以我们每次将l向前移动一个位置 扫描到一个数

    如果这个数没有出现过 标记该数已经出现过

    如果这个数已经出现过

    从后向前移动右指针r一直移动到l-r序列中没有重复数,

    所有移动过去的数标记他们的last[i]=l+1

    如果l扫描到1但是r没有扫描到1,从r当前位置往前扫描r标记book[r]=-1,

    表示r结尾的完美序列起点一直可以到第一个数

    这个方法的正确性证明如下:

    因为每次扫描左指针的时候都没有重复数字,

    所以直到找到重复数字为止(l+1)-r这段区间都没有重复数字

    也就是说从r一直往回找到与a[l]重复的那个位置(a数组表示原序列)为止

    中间扫过的所有数到l+1的位置都没有重复数字

    但它不能再往前扫描到l 因为从这个正在扫过的数到l+1中间经过了那个与a[l]重复的位置

    如果它再扫描到l 这个以l开始以正在扫描到的这个r和重复位置中间的某个数结尾的序列必然经过那个重复位置

    (因为r>那个与a[l]重复位置的编号)

    这样我们就可以在O(2n)的复杂度内初始化last数组

    同理,我们可以在O(n)的复杂度内初始化st数组

    同时注意dp数组存储的不是原序列而是st即可

    其实初始化last和st还有一个方法但我不会(逃)


    但是我们现在只是明确了问题的范围

    所以为了解决以某些点结尾的序列的起点在待查询的区间外,

    我们观察last数组 很明显last数组是单调递增

    证明如下:

    假设现在存在一段区间,其中该区间前两个点标记为l1,l2(l1<l2),后两个点标记为r2,r1(r2<r1)

    其中last[r1]=l1,last[r2]=l2

    大概就是存在这种一段完美序列完整包含了另一段完美序列的情况

    因为对于完美序列(l2,r2)一定有且只有一个l1(否则(l2,r2)就不是以r2结尾的最长完美序列,即last[r2]!=l2)

    但是由于(l2,r2)包含在(l1,r1)中

    所以完美序列(l2,r1)一定也有且只有一个l1

    所以(l1,r1)中有两个l1

    与(l1,r1)是完美序列矛盾,所以:

    对于任意的(r2,r1)(r2<r1),两点为结尾的完美序列分别的左端点(l2,l1)一定满足l2<l1.

    又因为l1=last[r1] l2=last[r2] l1<l2 r1>r2 所以last满足单调递增


     因为单调递增 而又因为第一部分的问题

    所以我们可以二分一个点mid 使得以mid及其左边的点为结尾的完美序列的

    左边界都在不在待查区间内 其右侧点为结尾的完美序列的左边界都在待查区间内

    其实就是这样

    这样对于一个区间完美序列的长度我们可以分为两个部分计算:

    mid及其左侧的部分,设待查区间左端点为l,则ans=max(x-l+1),

    其中x是mid及其左侧的点;

    这样做的正确性在于 因为last[x]<l 所以l--x这一段肯定在以x结尾的完美序列里

    也就是说它也是完美序列;

    又因为mid是离l最远满足last[x]<l的点 所以ans=mid-l+1;

    对于mid右侧的部分,ans=ask(mid+1,r),其中ask用ST表维护区间最大值;

    综上,对于二分出的分界点mid,有ans=max(mid-l+1,ask(mid+1,r));


    也就是说这个题的思路大概是这个样子:

    (1)通过观察到“完美序列的左端点不在待查区间内”的问题,明确该问题的

    范围(建立last数组);

    (2)利用分类讨论的思想,将“完美序列的左端点不在待查区间内”的情况

    和“完美区间内的左端点在待查区间内”的情况分别处理;

    (3)找到两种情况的分界线(二分mid);

    (4)讨论左端点不在待查区间内的情况,根据完美序列的性质(完美序列的子序列

    一定是完美序列)推出mid及其左侧点的求解方法;

    (5)讨论左端点在待查区间内的情况,将问题转化为研究过的问题(区间最大值),

    根据所求问题及已有的条件(last数组)初始化出st数组作为ST表的初值;

    (6)综合两部分答案,得出所求结果。

    放代码:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<map>
    #define int long long
    using namespace std;
    int n,m,a[2000500],dp[2000500][65],last[2000500],st[2000500];
    map<signed,int> book;//注意盈利值有可能是负数,所以利用带符号的signed类型作为下标(坑了我所有的分qwq)
    void init()
    {
        int l=n,r=n;
        memset(last,-1,sizeof last);
        memset(st,-1,sizeof st);
        for(l=n;l>=1;--l)//尺取法初始化last数组
        {
            if(book[a[l]])//已经扫描过这个点
            {
                while(a[r]!=a[l])//向前扫描到重复的点
                {
                    last[r]=l+1;//中间所有扫描过去的点标记以它结尾的完美序列的起点为l+1
                    book[a[r]]=0;//这个值现在不在序列中
                    --r;
                }
                last[r]=l+1;//标记最后一个重复的点
                --r;
            }
            book[a[l]]=1;//标记这个值进入了序列
        }
        for(int i=1;i<=n;i++)
        {
            if(~last[i])st[i]=i-last[i]+1;
            else st[i]=i;
        }
        for(int i=1;i<=n;i++)dp[i][0]=st[i];//注意应当以完美序列的长度作为初值
        for(int j=1;(1<<j)<=n;j++)
            for(int i=1;i+(1<<j)-1<=n;i++)
                dp[i][j]=max(dp[i][j-1],dp[i+(1<<j-1)][j-1]);
    }
    int ask(int l,int r)
    {
        int k=log2(r-l+1);
        return max(dp[l][k],dp[r-(1<<k)+1][k]);
    }
    signed main()
    {
    //    freopen("in.txt","r",stdin);
    //    freopen("out.txt","w",stdout);
        scanf("%lld%lld",&n,&m);
        for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
        init();
    //    for(int i=1;i<=n;i++)printf("%lld ",last[i]);
    //    puts("
    ");
        int x,y;
        for(int i=1;i<=m;i++)
        {
            scanf("%lld%lld",&x,&y);
            ++x;
            ++y;
            if(last[y]<x)
            {
                printf("%lld
    ",y-x+1);
                continue;
            }//如果所有的点都满足以该点结尾的完美序列起点不在待查区间内,直接退出二分
            if(last[x]==x)
            {
                printf("%lld
    ",ask(x,y));
                continue;
            }//同理,所有起点都在待查区间内
            int ll=x,rr=y,flag=-1,mid=x;
            while(ll<rr-1)
            {
                mid=(ll+rr)>>1;
                if(last[mid]>=x)rr=mid-1;
                else ll=mid;
            }
            if(last[mid]<x&&last[mid+1]>=x)printf("%lld
    ",max(mid-x+1,ask(mid+1,y)));
            else if(last[ll]<x&&last[ll+1]>=x)printf("%lld
    ",max(ll-x+1,ask(ll+1,y)));
            else if(last[rr]<x&&last[rr+1]>=x)printf("%lld
    ",max(rr-x+1,ask(rr+1,y)));//二分查找mid
        }
        return 0;
    }
    /*
    5 1
    -4 5 -9 3 4 
    1 2
    */

    本题思维难度较大,希望大家明白之后自行再写一遍。

    /*====年轻人,瞎搞是出不了省一的,这就是现实====*/
  • 相关阅读:
    springboot之静态资源放行的方法(记录)
    FastDFS之图片上传
    FastDFS安装部署
    Docker安装MySQL5.7.25
    123qwe
    Spring3 MVC 注解(一)---注解基本配置及@controller和 @RequestMapping 常用解释
    jQuery可见性过滤选择器
    jQuery属性过滤选择器
    jQuery内容过滤选择器
    jQuery子元素过滤选择器
  • 原文地址:https://www.cnblogs.com/qxds/p/11479803.html
Copyright © 2011-2022 走看看