最长非降子序列dp与贪心+二分做法与树状数组

18 篇文章 0 订阅
6 篇文章 0 订阅

dp:o(n^2)

设dp[i]为以i结尾的最长非降子序列,则dp[i]=max(dp[i],dp[j]+1),其中a[i]>=a[j]

贪心+二分o(nlogn)

用一个新的数组f记录选出的非降子序列,若a[i]>=f[top],则将a[i]插入f,更新top,否则由贪心策略,考虑用a[i]来替换f中第一个>=a[i]的数,使得f更具作为最长非降子序列的潜力。(注意这样得到的不一定真为非降的,因为替换的值可能为处于中间的值,并不满足元顺序,但长度等价)

e.g 4 0 5 8 10 1 8

4->0 -> 0 5 -> 0 5 8->0 5 8 10->0 1 8 10

#include<bits/stdc++.h>
using namespace std;
#define far(i,t,n) for(int i=t;i<n;++i)
typedef long long ll;
typedef unsigned long long ull;

int a[50050];
int f[50050];

int main()
{
    int n;
    cin>>n;
    far(i,0,n)
        scanf("%d",&a[i]);
    int l=1;
    f[0]=a[0];
    far(i,1,n)
    {
        if(a[i]>f[l-1])
        {
            f[l]=a[i];
            ++l;
        }
        else
        {
            int p=lower_bound(f,f+l,a[i])-f;
            f[p]=a[i];
        }
    }
    printf("%d\n",l);
}

获取LIS关键元素

即长度一样的LIS中公共元素

hdu6635:Nonsense Time

theme:给定长度为n的序列,开始时所有元素都被冻住,即不可用,然后没次解冻某个元素Api,问当前可用元素的最长非降子序列长度为多少 ?1<=n<=50000,1<=Ai<=n,1<=pi<=n

solution:逆向考虑。先求出所有元素的最长非降子序列,然后倒序删除元素,若被删除的元素不是上一个最长非降子序列的关键元素,则长度不变,冻结它就行,否则冻住它,再算一遍最长非降子序列。用贪心+二分算最长非降子序列复杂度为o(nlogn),由于数据随机,所以LIS期望长度为sqrt(n),所以总时间复杂度为o(sqrt(n)*n*logn).

#include<bits/stdc++.h>
typedef long long LL;
#define inf 0x3f3f3f3f
using namespace std;
#define far(i,s,n) for(int i=s;i<n;++i)

int a[50050],fidx[50050];
int dp[50050],idx[50050],belong[50050];//dp存LIS元素,idx[i]表示a[i]能在dp中的位置下标,belong[i]标识a[i]是否为LIS关键元素
int ans[50050];
int n;
void Find()//找LIS
{
    fill(dp,dp+n,inf);
    fill(idx,idx+n,-1);
    fill(belong,belong+n,0);
    int maxl=0;
    far(i,0,n)
    {
        if(a[i]!=inf)//表示a[i]没被冻住
        {
            int pos=lower_bound(dp,dp+n,a[i])-dp;
            if(dp[pos]==inf)//a[i]比dp里所有元素都大,则把它加在后面
            {
                dp[pos]=a[i];
                idx[i]=pos;
                maxl = pos;
            }
            else
            {
                dp[pos]=a[i];//用较小的值替换
                idx[i]=pos;
            }
        }
    }
    for(int i=n-1;i>=0;--i)//判断每个元素是否为关键元素,即每个该长度的LIS都有它
    {
        if(maxl<0)
            break;
        if(idx[i]==maxl)
        {
            belong[i]=1;
            --maxl;
        }
    }
}

int main()
{
    int Case;
    cin>>Case;
    while(Case--)
    {
        scanf("%d",&n);
        far(i,0,n)
            scanf("%d",&a[i]);
        Find();
        far(i,0,n)
        {
            scanf("%d",&fidx[i]);
            --fidx[i];
        }
        for(int i=n-1;i>=0;--i)
        {
            ans[i]=lower_bound(dp,dp+n,inf)-dp;
            if(belong[fidx[i]]==1)
            {
                a[fidx[i]]=inf;
                Find();
            }
            else
                a[fidx[i]]=inf;
        }
        far(i,0,n)
        {
            if(i==n-1)
                printf("%d\n",ans[i]);
            else
                printf("%d ",ans[i]);
        }
    }
    return 0;
}

用树状数组

对原数组排序后,依次插入,每个前缀和表示以该节点结尾的最长非降子序列的值。

#include<bits/stdc++.h>
#define far(i,t,n) for(int i=t;i<n;++i)
#define endl "\n"
#define spa " "
#define pk(x) push_back(x)
typedef long long ll;
typedef unsigned long long ull;
using namespace std;

ll tree[200020];
int lowbit(int x)
{
    return x&(-x);
}

void update(int x,ll var)
{
    while(x<200020)
    {
        tree[x]=max(tree[x],var);
        x+=lowbit(x);
    }
}

ll query(int x)
{
    ll ans=0;
    while(x)
    {
        ans=max(ans,tree[x]);
        x-=lowbit(x);
    }
    return ans;
}

ll a[200020];
vector<ll>v;

int main()
{
    int n;
    cin>>n;
    far(i,0,n)
    {
        scanf("%lld",&a[i]);
        v.pk(a[i]);
    }
    sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()),v.end());
    ll ans=0;
    far(i,0,n)
    {
        ll h;
        scanf("%lld",&h);
        int x=lower_bound(v.begin(),v.end(),a[i])-v.begin()+1;//树状数组下标从1开始
        ll tmp=query(x);
        ans=max(ans,tmp+h);
       // cout<<x<<" "<<tmp<<" "<<ans<<endl;
        update(x,tmp+h);
    }
    cout<<ans<<endl;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值