【HDU6602】Longest Subarray【线段树+分治】

题目大意:给出一串序列,询问最长的合法子串为多长,其中合法子串必须满足子串中[1,C]的数量大于等于K或者为0

题解:

定义右端点为包含某一点所需要的最小区间的右端点

那么初始化时就可以O(n)求出每个点的右端点

定义最小包含区间为某个点到其右端点的区间

定义不同的区间为当两个最小包含区间的最左边元素不同时两个区间为不同

那么不难想到,一个合法的子串,当且仅当所有最靠左边的不同的区间都被包含在子串内,此时子串合法

因此,不难想到当出现一个不合法区间时,将这个不合法区间的左端点作为分割点,分为左右两边进行递归操作

于是有了一个分治的做法,一开始询问子串[1,n]是否合法,若合法,则可能为答案,若不合法,则找到所有不合法的左端点,将其作为分割点分治下去

找不合法的左端点是复杂度瓶颈,我们考虑优化它

维护一个线段树,线段树维护最大的右端点,以及提供这个右端点的点在哪

那么不难想到每次只需要询问线段树[l,r]得到右端点看是否右端点大于r,大于则不合法,线段树返回的另一个值就是不合法点,将其作为分割点进行分治

考虑到所有相同的颜色我只需要最左边的还有用的,于是一开始时只需要把每个颜色最左边的区间加入线段树,分治时严格从左到右,删点时删除一个点的同时将删除点的颜色的下一个区间加入线段树,这样就能保证答案没有遗漏或者出错

因为每个点只会被加入一次和删除一次,所以时间复杂度为O(nlogn),可以通过此题

代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#define INF 1000000007
using namespace std;
int n,c,k,tans;
int a[100001],nxt[100001],hd[100001];
int minsb[100001];
struct node
{
  int p,minr;
}dat[100001*20];

void pushup(int pos)
{
    if(dat[pos<<1].minr>=dat[pos<<1|1].minr)dat[pos]=dat[pos<<1];
    else dat[pos]=dat[pos<<1|1];
}
void change(int l,int r,int p,int v,int pos)
{
    //printf("%d %d %d\n",l,r,p);
    if(l==r && l==p)
    {
      dat[pos]=(node){p,v};
      return;
    }
    int mid=l+r>>1;
    if(p<=mid)change(l,mid,p,v,pos<<1);
    else change(mid+1,r,p,v,pos<<1|1);
    pushup(pos);
}
node ask(int l,int r,int al,int ar,int pos)
{
    if(l==al && r==ar)return dat[pos];
    int mid=l+r>>1;
    node t1,t2;
    if(ar<=mid)return ask(l,mid,al,ar,pos<<1);
    if(al>mid)return ask(mid+1,r,al,ar,pos<<1|1);
    t1=ask(l,mid,al,mid,pos<<1);
    t2=ask(mid+1,r,mid+1,ar,pos<<1|1);
    if(t1.minr>=t2.minr)return t1;
    else return t2;
}
void work(int l,int r)
{
    //printf("%d %d\n",l,r);
    if(l>r)return;
    node td=ask(1,n,l,r,1);
    //printf("  %d %d\n",td.p,td.minr);
    if(l==r)
    {
      if(td.minr<=r)tans=max(tans,1);
      change(1,n,l,0,1);
      if(nxt[l])change(1,n,nxt[l],minsb[nxt[l]],1);
      return;
    }
    if(td.minr>r)
    {
      work(l,td.p-1);
      change(1,n,td.p,0,1);
      if(nxt[td.p])change(1,n,nxt[td.p],minsb[nxt[td.p]],1);
      work(td.p+1,r);
    }
    else 
    {
      tans=max(tans,r-l+1);
      for(int i=l;i<=r;i++)
      {
        //printf("*%d*  %d\n",i,nxt[i]);
        change(1,n,i,0,1);
        //printf("==-=-=-=-=-=-=\n");
        if(nxt[i])change(1,n,nxt[i],minsb[nxt[i]],1);
      }
      return;
    }
}
void init()
{
    memset(a,0,sizeof(a));
    memset(nxt,0,sizeof(nxt));
    memset(hd,0,sizeof(hd));
    memset(minsb,0,sizeof(minsb));
    for(int i=1;i<=n;i++)change(1,n,i,0,1);
    tans=0;
}
int main()
{
    while(scanf("%d%d%d",&n,&c,&k)!=EOF)
    {
      init();
      for(int i=1;i<=n;i++)scanf("%d",&a[i]);
      for(int i=n;i>0;i--)
      {
        nxt[i]=hd[a[i]];
        hd[a[i]]=i;
      }
      for(int i=1;i<=c;i++)
      {
        int t=hd[i],l=1,j;
        if(!t)continue;
        j=t;
        while(l<k && j)
        {
          j=nxt[j];
          if(j)l++;
        }
        if((!j) || l<k)
        {
          while(t){minsb[t]=INF;t=nxt[t];}
        }
        else
        {
          while(t && j){minsb[t]=j;j=nxt[j];t=nxt[t];}
          while(t){minsb[t]=INF;t=nxt[t];}
        }
      }
      for(int i=1;i<=c;i++)if(hd[i])change(1,n,hd[i],minsb[hd[i]],1);
      work(1,n);
      printf("%d\n",tans);
      //for(int i=1;i<=n;i++)printf("%d ",minsb[i]);
    }
    return 0;
}

心得:考场上很快就想到了区间的做法,但是最后维护线段树时只放最左边的想法一直没有突破导致浪费了一点时间,还需要更加努力啊

posted @ 2019-07-30 16:40  worcher  阅读(359)  评论(0编辑  收藏  举报