牛客网暑期ACM多校训练营(第五场)H:subseq(思维)

链接:https://www.nowcoder.com/acm/contest/143/H

时间限制:C/C++ 2秒,其他语言4秒
空间限制:C/C++ 262144K,其他语言524288K 64bit IO Format: %lld
题目描述
Kanade has an array a [ 1.. n ] , she define that an array b [ 1.. m ] is good if and only if it satisfy the following conditions:
1 <= b [ i ] <= n
b [ i ] < b [ i + 1 ] for every i between 1 and m 1
a [ b [ i ] ] < a [ b [ i + 1 ] ] for every i between 1 and m 1
m > 0
Now you need to find the k-th smallest lexicographically good array.

输入描述:
The first line has two integer n , k

The second line has n integer a [ i ]
输出描述:
If there is no solution, just only output -1, else output two lines, the first line has an integer m , the second line has m integer b [ i ]
示例1
输入
3 2
1 2 3
输出
2
1 2
示例2
输入
3 1000000000
1 2 3
输出
-1
备注:
1 <= n <= 5 10 5
1 <= k <= 10 18
1 <= a [ i ] <= 10 9

思路:题面很简洁。若a数组中有一个上升子序列,则b数组储存的是这个序列的下标。显然a数组有很多个不同的上升子序列,则b数组也有很多种。题目就是要你求出这些可能的b数组中,字典序第k小的是什么。

对于任意一个 a [ i ] ,我们定义 d [ i ] 是以 a [ i ] 为起点的上升子序列的个数。
d [ i ] = 1 + j = i + 1 n d [ j ] ( a [ j ] > a [ i ] ) 其中 1 代表只有 a [ i ] 这一个数的序列。
这过程可以用线段树累计区间和。
(因为d数组会爆long long ,所以不知道怎么用BIT处理。。。)

现在我们知道了以每个 a [ i ] 为起点的上升子序列的个数。

因为要保证答案是第k小,所以我们从下标最小的 a [ 1 ] 开始枚举起点.
d [ 1 ] >= k ,那以 a [ 1 ] 为起点满足条件,把 a [ 1 ] 的下标加入答案, k
然后往后找第一个 j 使 a [ j ] > a [ i ] ,判断 d [ j ] 是否大于等于 k
若小于 k ,则 k = d [ j ] (因为这些序列不满足条件)。
若大于 k 则把 j 加入答案
…….
以此类推,直到 k = 0

#include<bits/stdc++.h>
using namespace std;
const int MAX=5e5+10;
const int MOD=998244353;
const long long INF=1e18;
typedef long long ll;
vector<int>v;
int g(int x){return lower_bound(v.begin(),v.end(),x)-v.begin()+1;}
struct lenka
{
    int l,r;
    ll sum;
}A[MAX<<2];
void build(int k,int l,int r)
{
    A[k].l=l,A[k].r=r;
    A[k].sum=0;
    if(l==r)return;
    build(2*k,l,(l+r)/2);
    build(2*k+1,(l+r)/2+1,r);
}
void add(int k,int x,ll y)
{
    if(x==A[k].l&&x==A[k].r){A[k].sum=min(A[k].sum+y,INF);return;}
    if(x<=A[2*k].r)add(2*k,x,y);
    else add(2*k+1,x,y);
    A[k].sum=min(INF,A[2*k].sum+A[2*k+1].sum);//防爆long long
}
ll ask(int k,int x,int y)
{
    if(x>y)return 0;
    if(x==A[k].l&&y==A[k].r)return A[k].sum;
    if(y<=A[2*k].r)return ask(2*k,x,y);
    if(x>=A[2*k+1].l)return ask(2*k+1,x,y);
    return min(ask(2*k,x,A[2*k].r)+ask(2*k+1,A[2*k+1].l,y),INF);
}
int a[MAX];
ll d[MAX];
int main()
{
    ll n,k;
    cin>>n>>k;
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)v.push_back(a[i]);
    sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()),v.end());//将a数组中的值离散化
    build(1,1,v.size());
    for(int i=n;i>=1;i--)
    {
        ll x=ask(1,g(a[i])+1,v.size());//线段树查询并更新d数组
        d[i]=min(INF,x+1);
        add(1,g(a[i]),d[i]);
    }
    int st=-1;
    for(int i=1;i<=n;i++)//找到满足序列个数>=k的起点
    {
        if(k>d[i])k-=d[i];
        else
        {
            st=i;
            break;
        }
    }
    if(st==-1)puts("-1");
    else
    {
        v.clear();
        while(k)
        {
            v.push_back(st);
            k--;
            for(int j=st+1;j<=n;j++)
            {
                if(a[j]<=a[st])continue;
                if(k>d[j])k-=d[j];
                else
                {
                    st=j;
                    break;
                }
            }
        }
        cout<<v.size()<<endl;
        for(int i=0;i<v.size();i++)printf("%d%c",v[i],i==v.size()-1?'\n':' ');
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Mitsuha_/article/details/81366570