舒老师AK的hu测 T1. 迷失沃尔玛(dp+贪心)

61 篇文章 0 订阅
19 篇文章 0 订阅

版权属于舒老师,想要引用此题(包括题面)的朋友请联系博主

这里写图片描述
这里写图片描述

分析:
mmp,这道题做了不短的时间(这是什么语法。。。)

首先我们可以用 O(nlogn) O ( n l o g n ) 的复杂度计算出 f[i] f [ i ] (表示以 i i 为结尾的LIS)


以下就是我yy的过程了:

一开始有一个非常naive的想法:
把数组的每个位置视为一个点
每个点向在ta后面且比ta大的点连一条边
那么对于询问我们只需要在图中找到一条长度为Q的路径即可
(至于字典序最小,我们直接贪心构造即可)

但是这样边数会比较多,我们深入考虑能成为每个点后继的条件:
看一个简单的例子

    1   2   3   4   5
a=  2   5   6   4   3 

对于a[1],我们显然需要向 a[2] a [ 2 ] 连边,但是我们没有必要向 a[3] a [ 3 ] 连边
然而我们还是需要向 a[4],a[5] a [ 4 ] , a [ 5 ] 连边

一句话总结:从前往后扫,记 minn m i n n 为当前扫到的数字的最小值,我们只需要向小于 minn m i n n 且大于 a[i] a [ i ] 的元素连边即可
(也就是说,数值较大还靠后的元素就没有必要连边了

这样我们就可以得到每个结点的后继结点
贪心的,后继结点按照数值从小到大排序,这样我们遍历的时候就可以直接贪心构造解了

至此为止,如果我们要处理询问就需要遍历整个图
虽然边数已经减少了很多,但是还是达不到要求

所以我用 O(n2) O ( n 2 ) 的时间处理出了 len[i] l e n [ i ] ,表示以位置 i i 为起点的LIS

这样在构造解的时候,我们找到 len[i]>=Q l e n [ i ] >= Q a[i] a [ i ] 最小的元素作为起点
之后贪心构造答案即可:遍历后继结点,只有后继结点的 len+>=Q “ l e n + 当 前 答 案 长 度 >= Q ” ,我们才继续往下走

复杂度 O(nq) O ( n q )
(预处理不算啦)


可能有dalao会吐槽:
O(n2) O ( n 2 ) 处理出 len l e n 数组,不是太慢了吗

啊咧,确实。。。
不过这道题的标算是 O(nq) O ( n q ) 的(所以胖爷用 O(nqlogn) O ( n q l o g n ) 艹掉了标算。。。)

科学的预处理:

把整个序列翻转过来,求以每个结点为末尾的最长下降子序列
这样正过来就是以每个结点为起点的 LIS L I S

实际上这道题的标解贪心贪的更漂亮

从前往后,找到 len>=Q l e n >= Q 且数值最小的位置 i i ,作为答案的起点
之后的每一位一定在i之后,
我们在 i i 之后找到len+>=Q且数值最小的位置 j j <script type="math/tex" id="MathJax-Element-57">j</script>,作为答案的第二位
以此类推即可

tip

贴出我在考场上写的丑陋代码(毕竟也是想了好久的)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N=10002;
int n,g[N],maxlen,m,V[N],a[N],pos[N],nn,len[N];
int G[N][1002],size[N];
int X;

void LIS() {
    g[1]=a[1];
    maxlen=1;
    for (int i=2;i<=n;i++) {
        if (a[i]>g[maxlen]) {
            g[++maxlen]=a[i];
            continue;
        }
        int r=lower_bound(g+1,g+1+maxlen,a[i])-g;
        g[r]=a[i];
    }
}

int cmp(const int &A,const int &B) {
    return a[A]<a[B];                      //按值排序 
}

void prepare() {
    for (int i=1;i<=n;i++) size[i]=0;
    for (int i=n;i>=1;i--) {
        len[i]=1;
        int minn=n+1;                      //值大还靠后的就不用连边了 
        for (int j=i+1;j<=n;j++) 
            if (a[j]<minn&&a[j]>a[i]) {
                G[i][++size[i]]=j;         //G[i] 位置i的后继 
                minn=a[j];
                len[i]=max(len[i],len[j]+1);
                //len[i] 以i为起点的LIS 
            }
    }
    for (int i=1;i<n;i++) sort(G[i]+1,G[i]+1+size[i],cmp);
}

int ans[N];
bool flag;

void dfs(int t,int s) {
    if (t>=X) {
        flag=0;
        return;
    }
    if (!flag) return;
    for (int i=1;i<=size[s];i++)
        if (len[G[s][i]]+t>=X&&flag) {
            printf("%d ",V[a[G[s][i]]]);
            dfs(t+1,G[s][i]);
            if (!flag) return;
        }
    if (!flag) return;
}

void solve() {
    flag=1;
    for (int i=1;i<=nn-X+1;i++)
        if (len[pos[i]]>=X&&flag) {
            printf("%d ",V[a[pos[i]]]);
            dfs(1,pos[i]);
            printf("\n");
            break;  
        }
}

int main() {
    freopen("misswalmart.in","r",stdin);
    freopen("misswalmart.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;i<=n;i++) 
        scanf("%d",&a[i]),V[i]=a[i];                     //V[i]  记录i的原始值 
    sort(V+1,V+1+n);
    nn=unique(V+1,V+1+n)-V-1;
    for (int i=1;i<=n;i++) {
        a[i]=lower_bound(V+1,V+1+nn,a[i])-V;             //离散化 
        pos[a[i]]=(pos[a[i]]==0)? i:min(pos[a[i]],i);    //值最靠前的位置 
    }
    LIS();
    prepare();
    scanf("%d",&m);
    for (int i=1;i<=m;i++) {
        scanf("%d",&X);
        if (X>maxlen) {printf("LXALWAYSMISS\n");continue;}
        solve();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值