一、题目描述

  给你一个长度为 $n$ 的序列 $a_1\sim a_n$,$0 \le a_i \le 1\times 10^9$。

  现在有 $m$ 次操作,第 $i$ 次操作将位置 $p_i$ 的数变为 $v_i$,$1\le v_i\le 1\times 10^9$。

  操作仅对本次有效,并不会真的改变序列。求出每次操作后的最长严格上升子序列。

  数据范围:$1\le n,m\le 4\times 10^5$;


 二、解题思路:

  其实这个题就是想让你掌握树状数组求 $LIS$ 的方法。

  也许你会说:这不是小学生都会!是的,我就不会。

  实际上真的很简单,考虑到转移方程 $f_i=max_{j=1}^{i-1}f_j+1[a_j<a_i]$。

  很明显的线段树区间修改不就行了吗。

  大概是我学 $LIS$ 的时候还太年轻,什么都想不到。

  现在看来很显然,因为区间是前后缀所以可以用树状数组代替线段树。

  然后就容易求出以 $a_i$ 结尾的 $LIS$ 长度 $f_i$,以 $a_i$ 开头的 $LIS$ 长度 $g_i$。

  求 $g_i$ 的时候将 $a_i$  视为 $tot-a_i+1$ 倒着做就行了,$tot$ 为总的不同元素数量。

  不难发现一次修改后新的的 $LIS$ 长度范围在原来的 $[LIS - 1,LIS+1]$。

  如果 $+1$ 当前位置 $i$ 一定满足 $f_i+g_i-1=LIS+1$。

  这里的 $LIS$ 指原来的 $LIS$,$f_i,g_i$ 为修改后的 $f_i,g_i$。

  离线下来全部跑一边就可以求出新的 $f_i,g_i$。

  如果 $-1$ ,则新的 $f_i+g_i-1=LIS-1$,且位置 $i$ 为原来 $LIS$ 必经的点。

  怎么求某一个点是不是 $LIS$ 必经的点呢?这大概是这个题唯一的难点了。

  不过我当然是想不到的,直接上经典结论:

  $记以第\ i\ 个数结尾的\ LIS\ 为\ f_i,记以第\ i\ 个数开头的\ LIS\ 为\ g_i$

  $如果\ f_i+g_i-1=ans,则\ i\ 在某些\ LIS\ 中,且\ i\ 一定在\ LIS\ 的第\ f_i\ 位。(考虑反证,很简单)$

  $于是我们得到判断位置\ i\ 为\ LIS\ 的必经点的充要条件:$

  $f_i+g_i-1=ans\ 且不存在一个可以在\ LIS\ 中的\ j\not =i\ 满足\ f_j=f_i。$

  现在你按照上述结论跑就可以了,结论很好,也很简单,时间复杂度 $O(nlog_2^n)$。


 三、完整代码

 1 #include<iostream>
 2 #include<algorithm>
 3 #define N 800010
 4 #define rep(i,l,r) for(int i=l;i<=r;i++)
 5 #define per(i,r,l) for(int i=r;i>=l;i--)
 6 using namespace std;
 7 int n,m,num,lis,a[N],f[N],g[N],b[N],c[N],t[N],k[N],ans[N];
 8 int qry(int u){
 9     int res=0; while(u)res=max(res,c[u]),u-=u&-u; return res;
10 }
11 void upt(int u,int val){
12     while(u<=num) c[u]=max(c[u],val),u+=u&-u;
13 }
14 struct Query{
15     int pos,val,f,g,id;
16     bool operator < (const Query &t) const {
17         return pos<t.pos;
18     }
19 }q[N];
20 signed main(){
21     ios::sync_with_stdio(false);
22     cin.tie(0); cout.tie(0);
23     cin>>n>>m; rep(i,1,n) cin>>a[i],b[++num]=a[i];
24     rep(i,1,m) cin>>q[i].pos>>q[i].val,q[i].id=i;
25     rep(i,1,m) b[++num]=q[i].val;
26     sort(b+1,b+1+num); num=unique(b+1,b+1+num)-b-1;
27     rep(i,1,m) q[i].val=lower_bound(b+1,b+1+num,q[i].val)-b;
28     rep(i,1,n) a[i]=lower_bound(b+1,b+1+num,a[i])-b;
29     rep(i,1,n) f[i]=qry(a[i])+1,upt(a[i]+1,f[i]);
30     rep(i,1,num) c[i]=0;
31     per(i,n,1) g[i]=qry(num+1-a[i])+1,upt(num+2-a[i],g[i]);
32     rep(i,1,n) lis=max(lis,f[i]+g[i]-1);
33     rep(i,1,n) if(f[i]+g[i]-1==lis) t[f[i]]++;
34     rep(i,1,n) if(f[i]+g[i]-1==lis&&t[f[i]]==1) k[i]=1;
35     sort(q+1,q+1+m); rep(i,1,num) c[i]=0; int now=1;
36     rep(i,1,n){
37         while(now<=m&&q[now].pos==i)
38             q[now].f=qry(q[now].val)+1,now++;
39         upt(a[i]+1,qry(a[i])+1);
40     }
41     rep(i,1,num) c[i]=0; now=m;
42     per(i,n,1){
43         while(now>=1&&q[now].pos==i)
44             q[now].g=qry(num-q[now].val+1)+1,now--;
45         upt(num+2-a[i],qry(num+1-a[i])+1);
46     }
47     rep(i,1,m){
48         int res=q[i].f+q[i].g-1;
49         if(res>=lis) ans[q[i].id]=res;
50         else ans[q[i].id]=lis-k[q[i].pos];
51     }
52     rep(i,1,m) cout<<ans[i]<<'\n';
53     return 0;
54 }

四、写题心得:

  对 $LIS$ 又有了新的理解,收获如下:

  $1、掌握了一个点为序列\ LIS\ 必经点的充要条件,很厉害!=> Exp++!$

  $2、掌握了树状数组,线段树求\ LIS\ 的方法,现在可以输出一组合法的\ LIS\ 了!=> Exp++!$

  $NOIP\ RP++!$

posted on 2023-11-09 08:17  trh0630  阅读(7)  评论(0编辑  收藏  举报