CF 650D dp+离散+线段树

题目大意:

长度为 n ( ≤ 400   000 ) n(\le 400\,000) n(400000)的序列, m ( ≤ 400   000 ) m(\le 400\,000) m(400000)次互相无关的替换,每次会替换某一个元素,求每次替换之后的最长上升子序列长度

解题思路:

  • 首先计算, i i i为结尾的 1 ∼ i 1\sim i 1i位置的最长上升子序列 p r e [ i ] pre[i] pre[i],以及以 i i i开头的 i ∼ n i\sim n in位置的最长上升子序列 s u f [ i ] suf[i] suf[i],而计算这个的方法就是常见的求最长上升子序列方法

  • 而最长上升子序列长度是 m x = m a x { p r e [ i ] + s u f [ i ] − 1 } mx = max\{pre[i]+suf[i]-1\} mx=max{pre[i]+suf[i]1},对于每个位置 p r e [ i ] + s u f [ i ] − 1 = m x pre[i]+suf[i]-1=mx pre[i]+suf[i]1=mx,可以利用 n u m [ p r e [ i ] ] num[pre[i]] num[pre[i]]来记录可以组成 m x mx mx前缀长度的个数。对于某个 n u m [ j = = p r e [ i ] ] num[j==pre[i]] num[j==pre[i]]若为 1 1 1,则说明 i i i位置是所有最长上升子序列不可缺少的部分

  • 对于每次询问 { a , b } \{a, b\} {a,b},首先求包含该更新位置的最长上升子序列长度: l e n = m a x i < a , h [ i ] < b ( p r e [ i ] ) + 1 + m a x j > a , h [ j ] > b ( s u f [ j ] ) len=max_{i<a,h[i]<b}(pre[i])+1+max_{j>a,h[j]>b}(suf[j]) len=maxi<a,h[i]<b(pre[i])+1+maxj>a,h[j]>b(suf[j])

  • 对于这两个 m a x max max的求法,可以先将询问 b b b以及 h [ i ] h[i] h[i]进行离散化离线处理,然后利用线段树来处理即可,将 h [ i ] h[i] h[i]以及询问 b b b当作下标, p r e [ i ]   o r   s u f [ i ] pre[i] \, or \, suf[i] pre[i]orsuf[i]当作权值

  • 接下来 a a a位置,未更新之前,是否是最长上升子序列不可缺少的部分

  • 倘若不是:则答案是 m a x ( m x , l e n ) max(mx,len) max(mx,len)

  • 否则,答案是 m a x ( m x − 1 , l e n ) max(mx-1,len) max(mx1,len)

AC代码:

#include <bits/stdc++.h>
#define ft first
#define sd second
#define pb push_back
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0) //不能跟puts混用
#define seteps(N) fixed << setprecision(N)
#define endl "\n"
const int maxn = 8e5 + 10;
using namespace std;
typedef long long ll;
typedef double db;
typedef pair<int, int> pii;
const ll mod = 1e9 + 7;
int n, m;
int h[maxn], pre[maxn], suf[maxn]; //pre[i]代表1~i一定以i结尾的最长上升子序列长度,suf[i]代表i~n一定以i开头的最长上升子序列长度
int num[maxn]; //num[i]代表当前长度可以组成最长上升子序列长度的个数
int a[maxn], cnta;
struct Qes {
    int a, b, id;
    bool operator < (Qes qs1) {return a < qs1.a;}
} qes[maxn];
struct SegTree {
    int mx[maxn << 2];
    #define ls (rt << 1)
    #define rs (rt << 1 | 1)
    #define mc (lc + rc >> 1)
    void pushup(int rt) {mx[rt] = max(mx[ls], mx[rs]);}
    void update_point(int lc, int rc, int rt, int x, int y) {
        if (lc == rc) {
            mx[rt] = max(mx[rt], y);
            return;
        }
        if (x <= mc) update_point(lc, mc, ls, x, y);
        else update_point(mc + 1, rc, rs, x, y);
        pushup(rt);
    }
    int query(int lc, int rc, int rt, int L, int R) {
        if (L > R) return 0;
        if (L <= lc && rc <= R) return mx[rt];
        if (R < lc || rc < L) return 0;
        int res = 0;
        res = max(query(lc, mc, ls, L, R), query(mc + 1, rc, rs, L, R));
        return res;
    }
} sgt1, sgt2; //sgt1存的是前缀,sgt2存的是后缀长度
void init1() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &h[i]), a[++cnta] = h[i];
    for (int i = 1; i <= m; i++) scanf("%d%d", &qes[i].a, &qes[i].b), qes[i].id = i, a[++cnta] = qes[i].b;
    sort (qes + 1, qes + m + 1);
    sort (a + 1, a + cnta + 1);
    cnta = unique(a + 1, a + cnta + 1) - (a + 1);
    for (int i = 1; i <= n; i++) h[i] = lower_bound(a + 1, a + cnta + 1, h[i]) - a;
    for (int i = 1; i <= m; i++) qes[i].b = lower_bound(a + 1, a + cnta + 1, qes[i].b) - a;    
}
int d[maxn], lend, mx;
void init2() {
    /* 处理pre */
    for (int i = 1; i <= n; i++) {
        if (h[i] > d[lend]) d[++lend] = h[i], pre[i] = lend;
        else {
            int p = lower_bound(d + 1, d + lend + 1, h[i]) - d;
            pre[i] = p;
            d[p] = h[i]; 
        }
    }
    /* 处理suf, 相当于从后往前求最长下降子序列,倘若把h[i]变为负数,就变成求最长上升子序列*/
    lend = 0;
    d[0] = -1e9 - 10;
    for (int i = n; i >= 1; i--) {
        if (-h[i] > d[lend]) d[++lend] = -h[i], suf[i] = lend;
        else {
            int p = lower_bound(d + 1, d + lend + 1, -h[i]) - d;
            suf[i] = p;
            d[p] = -h[i];
        }
    }
    /* 处理num */
    mx = 0;
    for (int i = 1; i <= n; i++) mx = max(mx, pre[i] + suf[i] - 1);
    for (int i = 1; i <= n; i++) {
        if (pre[i] + suf[i] - 1 == mx)
            num[pre[i]]++;
    }
}
int res[maxn];
void solve() {
    int p = 1;
    for (int i = 1; i <= m; i++) res[i] = 1;
    for (int i = 1; i <= m; i++) { 
        while (p + 1 <= qes[i].a) {//把qes[i].a前的位置都更新
            sgt1.update_point(1, cnta, 1, h[p], pre[p]); 
            p++;
        }
        res[qes[i].id] += sgt1.query(1, cnta, 1, 1, qes[i].b - 1);  
    }
    p = n;
    for (int i = m; i >= 1; i--) {
        while (p - 1 >= qes[i].a) { //把qes[i].b后的位置都更新
            sgt2.update_point(1, cnta, 1, h[p], suf[p]);
            p--;
        }
        res[qes[i].id] += sgt2.query(1, cnta, 1, qes[i].b + 1, cnta);
    }
    for (int i = 1; i <= m; i++) {
        if (num[pre[qes[i].a]] == 1 && pre[qes[i].a] + suf[qes[i].a] - 1 == mx) res[qes[i].id] = max(res[qes[i].id], mx - 1);
        else res[qes[i].id] = max(res[qes[i].id], mx);
    }
    for (int i = 1; i <= m; i++) cout << res[i] << endl;
}
int main() {
    init1(); //输入以及离散化
    init2(); //求pre,suf以及num
    solve();
    return 0;
}
/*
15 1
76 9 32 82 40 91 46 5 12 69 44 97 30 13 29
4 73
*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值