BZOJ4553 - [TJOI2016]序列

Portal

Description

给出一个\(n(n\leq10^5)\)个数的数列\(\{a_n\}\)\(m(m\leq10^5)\)个形如\((x,y)\)的变化,表示\(a_x\)可以变成\(y\)。我们称一个子序列是合法的当且仅当其中至多一个数发生变化后,其仍然是一个不降序列。求最长合法子序列的长度。

Solution

\(a_i\)能变化的最小值为\(fr_i\),最大值为\(to_i\)\(dp[i]\)表示以\(a_i\)结尾的最长合法子序列的长度。则很容易得到:
\[ dp[i]=max\{dp[j]\}+1 \quad (j<i,to_j\leq a_i,a_j\leq fr_i) \] 时间复杂度为\(O(n^2)\)
考虑如何快速求出满足\(to_j\leq a_i,a_j\leq fr_i\)\(j\),可以用k-d树搞(可能被卡常),这里用CDQ分治解决。
每次考虑\(dp[L..mid]\)\(dp[mid+1..R]\)的影响。对于\(i\in[mid+1,R]\)记录三元组\((a_i,fr_i,i)\),对于\(j\in[L,mid]\)记录三元组\((to_j,a_j,j)\),排序后用线段树可搞。具体来说,线段树上区间\([L_0,R_0]\)维护满足\(a_j\in[L_0,R_0]\)\(dp[j]\)的最大值。排序保证了\(to_j\leq a_i\),并且询问处于最后。

时间复杂度\(O(nlog^2n)\)

Code

//序列
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
inline char gc()
{
    static char now[1<<16],*s,*t;
    if(s==t) {t=(s=now)+fread(now,1,1<<16,stdin); if(s==t) return EOF;}
    return *s++;
}
inline int read()
{
    int x=0; char ch=gc();
    while(ch<'0'||'9'<ch) ch=gc();
    while('0'<=ch&&ch<='9') x=x*10+ch-'0',ch=gc();
    return x;
}
int const N=1e5+10;
int const V=N;
int n,m;
struct rec{int a,fr,to,id;} r[N],r1[N];
int cmpM;
bool cmpR(rec x,rec y)
{
    int x1,x2,y1,y2;
    if(x.id<=cmpM) x1=x.to,x2=x.a;
    else x1=x.a,x2=x.fr;
    if(y.id<=cmpM) y1=y.to,y2=y.a;
    else y1=y.a,y2=y.fr;
    if(x1==y1) return x2==y2?x.id<y.id:x2<y2;
    else return x1<y1;
}
int dp[N];
#define Ls (p<<1)
#define Rs ((p<<1)|1)
int rt; int maxV[N<<2],tag[N<<2];
void update(int p) {maxV[p]=max(maxV[Ls],maxV[Rs]);}
void change(int p,int x) {maxV[p]=tag[p]=x;} 
void pushdw(int p) {if(tag[p]!=-1) change(Ls,tag[p]),change(Rs,tag[p]),tag[p]=-1;}
int L,R; int tr[N];
void ins(int p,int L0,int R0,int x)
{
    if(L==L0&&R0==L) {change(p,max(maxV[p],x)); return;}
    pushdw(p);
    int mid=L0+R0>>1;
    if(L<=mid) ins(Ls,L0,mid,x);
    else ins(Rs,mid+1,R0,x);
    update(p);
}
int query(int p,int L0,int R0)
{
    if(L<=L0&&R0<=R) return maxV[p];
    pushdw(p);
    int mid=L0+R0>>1,res=0;
    if(L<=mid) res=max(res,query(Ls,L0,mid));
    if(mid<R) res=max(res,query(Rs,mid+1,R0));
    return res;
}
void solve(int fr,int to)
{
    if(fr==to) return;
    int mid=fr+to>>1;
    solve(fr,mid);
    for(int i=fr;i<=to;i++) r1[i]=r[i];
    cmpM=mid; sort(r1+fr,r1+to+1,cmpR);
    change(rt,0);
    for(int i=fr;i<=to;i++)
    {
        int t=r1[i].id;
        if(t<=mid) L=r[t].a,ins(rt,1,V,dp[t]);
        else L=1,R=r[t].fr,dp[t]=max(dp[t],query(rt,1,V)+1);
    }
    solve(mid+1,to);
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=n;i++) r[i].fr=r[i].to=r[i].a=read(),r[i].id=i;
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read();
        r[x].fr=min(r[x].fr,y),r[x].to=max(r[x].to,y);
    }
    rt=1; memset(tag,-1,sizeof tag);
    for(int i=1;i<=n;i++) dp[i]=1;
    solve(1,n);
    int ans=0;
    for(int i=1;i<=n;i++) ans=max(ans,dp[i]);
    printf("%d\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/VisJiao/p/BZOJ4553.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值