BZOJ 1109: [POI2007]堆积木Klo 神分析, LIS, BIT, 二分

Description

  Mary在她的生日礼物中有一些积木。那些积木都是相同大小的立方体。每个积木上面都有一个数。Mary用他的
所有积木垒了一个高塔。妈妈告诉Mary游戏的目的是建一个塔,使得最多的积木在正确的位置。一个上面写有数i
的积木的正确位置是这个塔从下往上数第i个位置。Mary决定从现有的高塔中移走一些,使得有最多的积木在正确
的位置。请你告诉Mary她应该移走哪些积木。
Input

  第一行为一个数n,表示高塔的初始高度。第二行包含n个数a1,a2,…,an,表示从下到上每个积木上面的数。
(1<=n<=100000,1<=ai<=1000000)。
Output

  注意:请输出最多有多少点可以处在正确位置
Sample Input
5

1 1 2 5 4
Sample Output
3

解题方法: 我们先列一下普通的DP方程,

dp[i]=max(dp[j]+1)(j<i,a[j]<a[i],a[i]a[j]<=ij)

然后这里就是3个限制条件了?CDQ三维偏序?翻了翻题解,发现神思路的题目。观察三个限定条件。

1,j<i

2,a[j]<a[i]

3,a[i]a[j]<=ij

容易发现已知2,3可以推出1。
而1代表的是这n个数的排列顺序。
而2的条件即为最长上升子序列。
所以我们不妨把3看做这n个数的重新排列法则,之后满足2的条件即可。
所以我们只需要按照j−a[j]<=i−a[i]把n个数重新排列,接着求一个最长上升子序列长度即可。
需要注意的是,如果i−a[i]<0的话,那么显然这个数不可能与C序列中的某个数对应上,直接跳过即可。

LIS可以二分也可以用树状数组的方法,PO爷用的树状数组的方法,可以看PO爷博客,蒟蒻写了一个代码和博主几乎一样的二分版本的代码。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100010;
const int maxm = 1000010;
struct node{
    int x, y;
    node(){}
    node(int x, int y) : x(x), y(y) {}
    bool operator < (const node &rhs) const{
        if(x == rhs.x) return y < rhs.y;
        return x < rhs.x;
    }
}b[maxn];
int n, cnt, ans, d[maxm], a[maxn];

int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++){
        scanf("%d", &a[i]);
        if(i - a[i] < 0) continue;
        b[++cnt].x = i - a[i], b[cnt].y = a[i];
    }
    sort(b + 1, b + cnt + 1);
    memset(d, 0x3f, sizeof(d));
    for(int i = 1; i <= cnt; i++)
    {
        int l = 1, r = ans, len = 0;
        while(l <= r){
            int mid = (l + r) / 2;
            if(b[i].y > d[mid]){
                len = mid, l = mid + 1;
            }
            else{
                r = mid - 1;
            }
        }
        ans = max(ans, len + 1);
        d[len + 1] = min(d[len + 1], b[i].y);
    }
    cout << ans << endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值