BZOJ 1046 HAOI 2007 上升序列 -- DP 贪心

如果能想到贪心那么这道题就不难了。假设我们求出f[i]表示以i为第一个数的最长上升子序列长度,那么为了使字典序最小,我们可以采用这样的贪心策略:当前询问的长度为Q,我们枚举第一个i,使得f[i] >= Q && a[i] > last,找到i之后:Q–, last = a[i],然后继续枚举。这个策略的正确性是很显然的。读入一个Q之后先判断是否有解,因为如果存在一个长度为L的上升序列,那么必定存在长度为1~L-1的上升序列,所以只需要判断Q与最长上升序列长度即可。复杂度是O(NM)

那么剩下的问题就是求f数组了。求LIS的方法有很多,这道题给了10s,或许N^2能卡过。我使用的NlogN的做法是用线段树优化一下。需要先离散化。

#include <cstdio>
#include <cstring>
#include <algorithm>
#define M 10005
#define lc (p<<1)
#define rc (p<<1|1)
#define mid (l+r>>1)
#define LC lc,l,mid
#define RC rc,mid+1,r
#define va first
#define id second
using namespace std;
typedef pair<int,int> PII;

int n, m, L, R, C, Maxlen, a[M], A[M], f[M], tag[M<<2], mx[M<<2];
PII Sort[M];

void get(int &x)
{
    char c = getchar(); x = 0;
    while(c < '0' || c > '9') c = getchar();
    while(c <= '9' && c >= '0') x = x*10+c-48, c = getchar();
}
void put(int x)
{
    char s[10]; int cnt = 0;
    while(x) s[++cnt] = (x%10)+48, x /= 10;
    while(cnt) putchar(s[cnt--]); 
}

void pushdown(int p)
{
    if(!tag[p]) return ;
    tag[lc] = max(tag[lc], tag[p]);
    tag[rc] = max(tag[rc], tag[p]);
    mx[lc] = max(mx[lc], tag[lc]);
    mx[rc] = max(mx[rc], tag[rc]);
    tag[p] = 0;
}

void change(int p, int l, int r)
{
    if(L <= l && r <= R)
    {
        tag[p] = max(tag[p], C);
        mx[p] = max(tag[p], mx[p]);
        return ;
    }
    pushdown(p);    
    if(L <= mid) change(LC);
    if(R >  mid) change(RC);
    mx[p] = max(mx[lc], mx[rc]);
}

int query(int p, int l, int r)
{
    if(L <= l && r <= R) return mx[p];
    pushdown(p);
    int res = 0;
    if(L <= mid) res = max(res, query(LC));
    if(R >  mid) res = max(res, query(RC));
    return res;
}

int main()
{
    get(n);
    for(int i = 1; i <= n; i++) 
    {
        get(a[i]); A[i] = a[i];
        Sort[i].va = a[i];
        Sort[i].id = i;
    }
    sort(Sort+1, Sort+n+1);

    int t = 0;
    for(int i = 1; i <= n; i++)
    {
        if(Sort[i].va != Sort[i-1].va || !t) t++;
        a[Sort[i].id] = t;
    }

    f[n] = 1;
    L = a[n], R = n, C = f[n];
    change(1, 1, n);
    for(int i = n-1; i; i--)
    {
        L = a[i]+1, R = n;
        f[i] = query(1, 1, n) + 1;
        L = R = a[i], C = f[i]; change(1, 1, n);
    }

    L = 1, R = n; Maxlen = query(1, 1, n);

    get(m);
    while(m--)
    {
        int q; get(q);
        if(q <= Maxlen)
        {
            int last = 0;
            for(int i = 1; i <= n && q; i++)
            if(a[i] > last && f[i] >= q)
            {
                last = a[i], q--;
                put(A[i]); 
                if(q) putchar(' ');
            }
        }
        else printf("Impossible");
        if(m) putchar('\n');
    }

    return 0;
}

//纪念第一次在Linux下写题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值