YbtOJ NOIP模拟 Sequence (dp+问题转移)

题意

给⼀个⻓度为 𝑛 的序列 𝑎 ,你可以删去任意多个数 给⼀个⻓度为 𝑛 的序列 𝑎,你可以删去任意多个数 度为n的序列a,你可以删去任意多个数
最⼤化剩余序列中,数值等于位置标号的个数。 最⼤化剩余序列中,数值等于位置标号的个数。 化剩余序列中,数值等于位置标号的个数。

input
5
1 1 2 5 4
output(可变为1 2 5 4)
3
subtask

对于 50% 的数据, n ≤ 1 e 3 n\le1e3 n1e3
​对于 100% 的数据, n ≤ 5 e 5 n\le5e5 n5e5;
其中 a i ≤ 1 e 9 a_i\le1e9 ai1e9 ;

solution

首先发现贪心不可行,锁定dp,很容易想到一个线性dp的做法:
令状态 f i f_i fi 表示处理 1 1 1 i i i 这些数,并且处理后的序列以 a i a_i ai 结尾且数字 a i a_i ai 移动到位置 a i a_i ai 上时的最大答案。 其中由于任意 a i a_i ai的位置只能往前移动,所以只有满足 a i ≤ i a_i\le i aii的状态才合法。
转移易得 f i = max ⁡ ( f j + 1 ) f_i=\max(f_j+1) fi=max(fj+1)
其中转移条件为:

{ j < i a j < a i j − a j ≤ i − a i   \left\{ \begin{array}{rcl} j<i\\ a_j<a_i\\ j-a_j\le i-a_i \ \end{array} \right. j<iaj<aijajiai 

条件很好理解: f i f_i fi由于下标 i i i的递增只能从小于 a i a_i ai a j a_j aj的状态转移来;而让 数字 a j a_j aj 移动到位置 a j a_j aj所需删数的数量不能超过 a i a_i ai的,即 j j j位置前删除的数不能超过 i i i位置前删除的数。
初态为 d p i = 1 ( a i ≤ i ) dp_i=1(a_i\le i) dpi=1(aii),因为对于任意一个满足 a i ≤ i a_i\le i aii的数都一定可以从位置 i i i 移动到位置 a i a_i ai
那么就有了一个朴素转移的 O ( n 2 ) O(n^2) O(n2)的dp,可以过掉subtask1
//50pts subtask1
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+10;
int n,a[maxn],f[maxn],ans;
signed main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
    	cin>>a[i];
    	if(a[i]<=i) f[i]=1,ans=1;
    }
    for(int i=1;i<=n;i++)
        if(a[i]<=i)
            for(int j=1;j<i;j++)
                if(a[j]<a[i]&&a[i]-a[j]<=i-j)
                    f[i]=max(f[i],f[j]+1),ans=max(ans,f[i]);
    cout<<ans<<endl;
    return 0;
}
那么怎么优化呢,可以从转移条件入手

{ j < i a j < a i j − a j ≤ i − a i   \left\{ \begin{array}{rcl} j<i\\ a_j<a_i\\ j-a_j\le i-a_i \ \end{array} \right. j<iaj<aijajiai 

对于这种类似三维偏序的转移,可以用二分数据结构或者CDQ分治优化,可以做到 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n),虽然本题数据不能AC,这种优化方式还是很值得学习的。
进一步的,凭借 小学 学习的不等式,很容易得到后两个不等式是第一个的充分不必要条件,所以只需要考虑后两个不等式即可

{ a j < a i j − a j ≤ i − a i   \left\{ \begin{array}{rcl} a_j<a_i\\ j-a_j\le i-a_i \ \end{array} \right. {aj<aijajiai 

采用二维偏序的思想,按照 a i a_i ai 为关键字排序,这样一定满足在新序列从前向后转移时, a j < a i 且 j < i a_j<a_i且j<i aj<aij<i。又由于每次转移贡献为1,所以只要在新序列的顺序中以 ( i − a i ) (i-a_i) (iai) 为元素值求最长不降序列,其长度即为所求答案。
人尽皆知的,最长不降序列问题可以通过树状数组或者单调栈做到 O ( n log ⁡ n ) O(n\log n) O(nlogn),就能AC此题。
//100pts subtask2
#include <bits/stdc++.h>
using namespace std;
const int maxn=5e5+10;
const int INF=0x3f3f3f3f;
struct node{int p, a,val;}a[maxn];
int n,m,h[maxn];
inline bool cmp(const node &A,const node &B)
{
    if(A.a==B.a) return A.p>B.p;
    else return A.a<B.a;
}
signed main()
{
    memset(h, 0x3f, sizeof(h));
    cin>>n;int x;
    for(int i=1;i<=n;i++)
        cin>>x,a[i]={i,x,i-x};
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++)
        if(a[i].val>=0)
            h[(upper_bound(h+1,h+n+1,a[i].val)-h)]=a[i].val;
    for (int i=n;i>=0;i--)
        if(h[i]<INF)
            return cout<<i<<endl,0;
    cout<<0<<endl;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值