「题解」:[BZOJ4358]permu

问题: permu

时间限制: 30 Sec  内存限制: 512 MB

题面


题目描述

给出一个长度为n的排列P(P1,P2,...Pn),以及m个询问。每次询问某个区间[l,r]中,最长的值域

连续段长度。

输入格式

第一行两个整数n,m。

接下来一行n个整数,描述P。

接下来m行,每行两个整数l,r,描述一组询问。

输出格式

对于每组询问,输出一行一个整数,描述答案。

样例输入

8 3
3 1 7 2 5 8 6 4
1 4
5 8
1 7

样例输出

3
3
4

题解


 

线段树+莫队。

考虑维护区间连续值域长度最大值。

首先显然莫队。(毕竟是蒟蒻我在莫队专题里遇到的题目……

序列操作、多次询问,最关键的是无修!(没学过带修莫队不行啊 /理直气壮)

于是考虑怎么在不同区间内转移。

我们先种一棵值域线段树维护点的存在与否。

我们假设li为某段区间从左边界开始的最长值域连续段,ri为从右边界开始的最长值域连续段,mi为区间中最长值域连续段,考虑如何转移。

当push_up的时候,li由左儿子的li转移而来。特殊地,我们考虑左儿子整个区间连续,则li由左儿子的区间大小加上右儿子的li转移而来。

同理,ri由右儿子的ri转移。特殊情况为右儿子的区间大小加上左儿子的ri。

考虑mi的转移。显然mi由左儿子的mi和右儿子的mi以及左儿子的ri与右儿子的li组成的新区间转移而来。

注意我们开的是值域线段树,因此可以保证区间的连续性。

于是线段树维护3个标记我们就可以方便的进行转移了。

然后就是莫队板子。不断在线段树中插入点,删除点并进行转移统计答案。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define rint register int
using namespace std;
int n,m,a[50004],tk[50004],belong[50004],l,r,sum_q,ans[50004];
struct node1{int li,ri,mi,size;}t[800005];
struct node2{int l,r,id;}que[50004];
inline bool cmp(node2 a,node2 b)
{
    return (belong[a.l]^belong[b.l])?belong[a.l]<belong[b.l]:((belong[a.l]&1)?a.r<b.r:a.r>b.r);
}
inline void build(rint k,rint l,rint r)
{
    if(l==r){t[k].size=1;return ;}
    int mid=(l+r)>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
    t[k].size=t[k<<1].size+t[k<<1|1].size;
    return ;
}
inline void update(rint k)
{
    t[k].li=t[k<<1].li;t[k].ri=t[k<<1|1].ri;
    if(t[k<<1].li==t[k<<1].size)t[k].li+=t[k<<1|1].li;
    if(t[k<<1|1].ri==t[k<<1|1].size)t[k].ri+=t[k<<1].ri;
//    cout<<"left:"<<t[k<<1].mi<<" right:"<<t[k<<1|1].mi<<" mid:"<<t[k<<1].ri+t[k<<1|1].li<<endl;
    t[k].mi=max(max(t[k<<1].mi,t[k<<1|1].mi),t[k<<1].ri+t[k<<1|1].li);
}
inline void change(rint k,rint p,rint u,rint l,rint r)
{
//    cout<<"change:"<<p<<endl;
    if(l==u&&r==u)
    {
//        cout<<p<<endl;
        t[k].mi=p;t[k].li=p;t[k].ri=p;
//        cout<<"mi:"<<t[k].mi<<endl;
        return ;
    }
    rint mid=(l+r)>>1;
    if(u<=mid)change(k<<1,p,u,l,mid);
    else change(k<<1|1,p,u,mid+1,r);
    update(k);//cout<<t[k].mi<<endl;
    return ;
}
int main()
{
    scanf("%d %d",&n,&m);
    sum_q=(int)sqrt(n);
    for(rint i=1;i<=n;++i)
    {
        scanf("%d",&a[i]);
        belong[i]=i/sum_q+1;
    }
    build(1,1,n);
    for(rint i=1;i<=m;++i)
    {
        scanf("%d %d",&que[i].l,&que[i].r);
        que[i].id=i;
    }
    sort(que+1,que+m+1,cmp);
    for(rint i=que[1].l;i<=que[1].r;++i)
    {
        tk[a[i]]++;
        if(tk[a[i]]==1)change(1,1,a[i],1,n);
    }
    ans[que[1].id]=t[1].mi;
    l=que[1].l,r=que[1].r;
    for(rint i=2;i<=m;++i)
    {
//        cout<<l<<" "<<que[i].l<<endl;
        while(l<que[i].l){tk[a[l]]--;if(tk[a[l]]==0)change(1,0,a[l],1,n);l++;}
//        cout<<l<<" "<<que[i].l<<endl;
        while(l>que[i].l){l--;tk[a[l]]++;if(tk[a[l]]==1)change(1,1,a[l],1,n);}
        while(r<que[i].r){r++;tk[a[r]]++;if(tk[a[r]]==1)change(1,1,a[r],1,n);}
        while(r>que[i].r){tk[a[r]]--;if(tk[a[r]]==0)change(1,0,a[r],1,n);r--;}
        ans[que[i].id]=t[1].mi;
    }
    for(rint i=1;i<=m;++i)
        cout<<ans[i]<<endl;
    return 0;
}
View Code

最开始没设初始区间调了半个小时……

ps:此题需要卡常,最好使用奇偶性排序法QAQ(18000(TLE63)->12000(AC))

 

posted @ 2019-08-03 19:59  hzoi_Joe  阅读(310)  评论(2编辑  收藏  举报