BZOJ 4904: [Ctsc2017]最长上升子序列 Young tableau

title

BZOJ 4904
LUOGU 3774
Description

猪小侠最近学习了最长上升子序列的相关知识。对于一个整数序列 A=(a1,a2,?,ak),定义A 的子序列为:从A 中删除若干个元素后(允许不删,也允许将所有k 个元素都删除),剩下的元素按照原来的顺序所组成的序列。如果这个子序列的元素从左到右严格递增,则称它为A 的一个上升子序列。其中包含元素数量最多的上升子序列称为A 的最长上升子序列。例如,(2,4,5,6)和 (1,4,5,6)都是(2,1,1,4,7,5,6)的最长上升子序列,长度都为 4。现在猪小侠遇到了这样一个问题:给定一个序列 Bm=(b1,b2,?,bm),设C 是 Bm的子序列,且C 的最长上升子序列的长度不超过k,则C 的长度最大能是多少?猪小侠觉得这个问题太简单了,缺乏挑战,他决定提出一个更难的问题。于是他给了你这样一个序列 B=(b1,b2,…,bn),以及若干次询问。每次询问会给定两个整数m 和k,你需要对于B 序列的前m 个元素构成的序列 Bm=(b1,b2,?,bm)和k 回答上述问题。

Input

第一行两个整数 n,q其中 n是序列B 的长度,q是询问次数。
第二行是空格隔开的 n个正整数 b1,b2,?,bn
接下来 q行,其中第 i行包含两个整数 mi,ki,表示对 m=mi,k=ki 进行询问。
N<=50000

Output

输出共 q行,按顺序每行一个整数作为回答。

Sample Input

11 6
9 6 3 1 5 12 8 4 2 2 2
5 1
7 2
9 1
9 2
11 1
11 11

Sample Output

4
6
5
8
7
11

analysis

刚开始是看着网络流标签过去的,看到多次询问就有些疑问了,网络流的复杂度不够啊!

虽然之前也曾碰到过一道网络流题目多次询问,但是那道题是可以转化的,这道题就不太行了,无法转化成一张图,所以就去翻了一下题解,看到了一个久违的名词:杨氏矩阵。

学了一下,发现是根据这个什么 \(Dilworth\) 定理,最小链覆盖=最长反链。 问题转化为\(k\) 个最小不上升序列能覆盖的最大数的个数

利用杨氏矩阵,我们可以轻松得到这个值。 不过注意杨氏矩阵的插入是 \(O(n)\)的,这时候有个定理,就是把杨氏矩阵维护东西的大小比较方式改变一下,会得到置换原矩阵后的矩阵。

于是我们原矩阵只维护\(O(\sqrt n)\)行,查询前kk行时我们只查询前 \(\sqrt n\) 行,剩下的就是置换矩阵的某一列。

发现置换矩阵也只用维护前 \(\sqrt n\) 行,于是结合二分即可做到\(O(n\sqrt nlog⁡n+qlog⁡n)\) 的复杂度。不过不好卡,直接暴力插入杨氏矩阵也可以通过。

大部分内容来自DZYO

code

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e4+10,maxq=2e5+10,S=233;//S=sqrt(maxn)

char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
    x=0;
    T f=1, ch=getchar();
    while (!isdigit(ch) && ch^'-') ch=getchar();
    if (ch=='-') f=-1, ch=getchar();
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}

template<typename T>inline void write(T x)
{
    if (!x) { putchar('0'); return ; }
    if (x<0) putchar('-'), x=-x;
    T num=0, ch[20];
    while (x) ch[++num]=x%10+48, x/=10;
    while (num) putchar(ch[num--]);
}

struct Orz{int m,k,id;}o[maxq];
inline bool cmp(Orz a,Orz b)
{
    return a.m<b.m;
}

int n,q;
namespace A
{
    int a[S][maxn];
    inline void insert(int v,int x=1,int y=n)
    {
        if (x>=S) return ;
        y=min(y,a[x][0]);
        while (y && a[x][y]<v) --y;
        ++y;
        if (!a[x][y]) ++a[x][0],a[x][y]=v;
        else insert(a[x][y],x+1,y),a[x][y]=v;
    }
}

namespace B
{
    int a[S][maxn],tree[maxn];
    inline int lowbit(int x) { return x & -x; }
    inline void add(int x) { while (x<=n) ++tree[x],x+=lowbit(x); }
    inline int sum(int x) { int ans=0; while (x) ans+=tree[x],x-=lowbit(x); return ans; }
    inline int ask(int l,int r) { return sum(r)-sum(l-1); }//BIT比前缀和好些?
    inline void insert(int v,int x=1,int y=n)
    {
        if (x>=S) return ;
        y=min(y,a[x][0]);
        while (y && a[x][y]>=v) --y;
        ++y;
        if (!a[x][y]) ++a[x][0],a[x][y]=v,add(y);
        else insert(a[x][y],x+1,y),a[x][y]=v;
    }
}

inline int query(int x)
{
    int ans=0;
    if (x<S)
        for (int i=1; i<=x && A::a[i][0]; ++i) ans+=A::a[i][0];
    else
    {
        for (int i=1; i<S; ++i) ans+=A::a[i][0];
        ans+=B::ask(S,x);
    }
    return ans;
}

int b[maxn],ans[maxq];
int main()
{
    read(n);read(q);
    for (int i=1; i<=n; ++i) read(b[i]);
    for (int i=1; i<=q; ++i) read(o[i].m),read(o[i].k),o[i].id=i;
    sort(o+1,o+q+1,cmp);
    for (int i=1,j=1; i<=n && j<=q; ++i)
    {
        A::insert(b[i]),B::insert(b[i]);
        while (j<=q && o[j].m==i) ans[o[j].id]=query(o[j].k),++j;
    }
    for (int i=1; i<=q; ++i) write(ans[i]),puts("");
    return 0;
}

转载于:https://www.cnblogs.com/G-hsm/p/11321708.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值