例题51Nod-1376,一个经典问题,给出一个序列问该序列的LIS以及LIS的数量。

这里我学习了两种解法,思路和代码都是参考这两位大佬的:

https://www.cnblogs.com/reverymoon/p/9496040.html

https://www.cnblogs.com/RabbitHu/archive/2017/11/02/51nod1376.html

首先是先分析一下问题,求LIS是一个很基础的问题了,用得最多的是nlogn的解法这里就不讲了。当我们求出序列a[i]的LIS为f[i]时,我们应该从哪里得到LIS的数量s[i]呢?

显然应该是 s[i]=sigma(s[j])(j<i,a[j]<a[i],f[j]=f[i]-1),即满足这三个条件的s[j]的和。因为我们是从左到右做,所以第一个条件满足,然后问题就是怎么能快速找到a[j]<a[i]且f[j]=f[i]-1的s[j]的和呢?

这里博主学习到两种解法,先将树状数组解法:将离散化后的a[i]作为下标在树状数组上建树,那么此时a[i]的前缀就必定满足a[j]<a[i]了,同时树状数组维护的是以a[i]结尾的LIS长度f[i]和a[i]的LIS数量s[i]。当求解a[i]时,我们在BIT上查询a[i]-1前缀的最长LIS同时累加最长LIS的数量,那么返回的结果就是后面一个数可以接上a[i]这个数的最长LIS和它的数量(这句话是重点。所以f[i]=返回结果+1,s[i]=返回结果数量。代码实现是学习上面博客大佬,带有一些小技巧。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int N=5e4+;
  4. const int P=1e9+;
  5. int n,m,a[N],b[N];
  6. struct dat{
  7. int len,sum;
  8. }bit[N],dp[N];
  9.  
  10. void upd(dat &x,dat y) { //计算y对x的影响
  11. if (x.len>y.len) return;
  12. else if (x.len<y.len) x=y;
  13. else x.sum+=y.sum,x.sum%=P;
  14. }
  15.  
  16. void update(int x,dat v) {
  17. for (;x<=m;x+=x&-x) upd(bit[x],v);
  18. }
  19.  
  20. dat query(int x) {
  21. dat ret=(dat){,};
  22. for (;x;x-=x&-x) upd(ret,bit[x]);
  23. return ret;
  24. }
  25.  
  26. int main()
  27. {
  28. cin>>n;
  29. for (int i=;i<=n;i++) scanf("%d",&a[i]),b[i]=a[i];
  30. sort(b+,b+n+);
  31. m=unique(b+,b+n+)-(b+);
  32. for (int i=;i<=n;i++) a[i]=lower_bound(b+,b+m+,a[i])-b;
  33. //update(0,(dat){0,1}); 不能这样写会死循环
  34. for (int i=;i<=n;i++) {
  35. dp[i]=query(a[i]-); dp[i].len++;
  36. update(a[i],dp[i]);
  37. }
  38. dat ans=(dat){,};
  39. for (int i=;i<=n;i++) upd(ans,dp[i]);
  40. cout<<ans.sum<<endl;
  41. return ;
  42. }

然后是同一道题的Vector+二分的解法。这种解法会更加容易理解,还是看上面三个条件(j<i,a[j]<a[i],f[j]=f[i]-1),然后第一个条件不用管考虑后两个条件。在学习nlogn的LIS解法的时候应该有学习到:从左到右同等长度的LIS末尾数字应该是单调不增的,基于这个我们可以考虑把前面的a[j]按照f[j]大小分组,同等LIS长度的元素a[j]存在同一个vector里面,然后这个vector的元素应该是单调不增的,那么我们就可以通过二分查找在f[i]-1的这一个vector里面快速地找到所有的<a[i]的元素,同时也就是快速算出满足上诉三个条件的s[j]总和。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int N=5e4+;
  4. const int P=1e9+;
  5. const int INF=0x3f3f3f3f;
  6. int n,m,a[N],g[N],f[N],s[N];
  7. vector<int> len[N],sum[N]; //len[i]存放LIS为i的数a[i],sum[i]存放a[i]对应的LIS数量
  8. //显然len[i]的元素是单调不增的
  9.  
  10. int Find(int p,int x) { //二分len=p的最后一个>=x的位置,这个位置后面的就是答案
  11. int l=,r=len[p].size()-,mid;
  12. while (l<r) {
  13. mid=l+r+>>;
  14. if (len[p][mid]>=x) l=mid;
  15. else r=mid-;
  16. }
  17. return ((sum[p].back()-sum[p][l])%P+P)%P;
  18. }
  19.  
  20. int main()
  21. {
  22. cin>>n;
  23. for (int i=;i<=n;i++) scanf("%d",&a[i]);
  24. memset(g,0x3f,sizeof(g));
  25. for (int i=;i<=n;i++) len[i].push_back(INF),sum[i].push_back();
  26. len[].push_back(-INF),sum[].push_back();
  27. int Max=,ans=;
  28. for (int i=;i<=n;i++) {
  29. int t=lower_bound(g+,g+n+,a[i])-g;
  30. g[t]=a[i];
  31. f[i]=t; s[i]=Find(t-,a[i]);
  32. len[t].push_back(a[i]); sum[t].push_back((sum[t].back()+s[i])%P);
  33.  
  34. if (f[i]>Max) Max=f[i],ans=s[i];
  35. else if (f[i]==Max) ans=(ans+s[i])%P;
  36. }
  37. cout<<ans<<endl;
  38. return ;
  39. }

题目练习:

2019计蒜之道 复赛 外教 Michale 变身大熊猫:https://nanti.jisuanke.com/t/39611

容易想到a[i]会在LIS的概率就是:(必须经过a[i]的LIS数量)/(所有的LIS数量)  。所有的LIS数量好求,主要问题就是怎么求必须经过a[i]的LIS的数量,这个问题也不难想,就是  从左到右以a[i]结尾的LIS数量*从右到左为a[i]为结尾的最长下降子序列数量。这就用到上面的知识了。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int P=;
  4. const int N=5e5+;
  5. typedef long long LL;
  6. int n,m,a[N],b[N],c[N];
  7.  
  8. struct dat{
  9. int len,sum;
  10. }dp1[N],dp2[N],bit[N];
  11. void upd(dat &x,dat y) {
  12. if (x.len>y.len) return;
  13. else if (x.len<y.len) x=y;
  14. else x.sum+=y.sum,x.sum%=P;
  15. }
  16. void update(int x,dat v) {
  17. for (;x<=m;x+=x&-x) upd(bit[x],v);
  18. }
  19. dat query(int x) {
  20. dat ret=(dat){,};
  21. for (;x;x-=x&-x) upd(ret,bit[x]);
  22. return ret;
  23. }
  24.  
  25. int power(int x,int p) {
  26. int ret=;
  27. for (;p;p>>=) {
  28. if (p&) ret=((LL)ret*x)%P;
  29. x=((LL)x*x)%P;
  30. }
  31. return ret;
  32. }
  33.  
  34. int main()
  35. {
  36. cin>>n;
  37. for (int i=;i<=n;i++) scanf("%d",&a[i]),b[i]=a[i];
  38. sort(b+,b+n+);
  39. m=unique(b+,b+n+)-(b+);
  40. for (int i=;i<=n;i++) c[i]=lower_bound(b+,b+m+,a[i])-b;
  41.  
  42. dat ans=(dat){,};
  43. memset(bit,,sizeof(bit));
  44. for (int i=;i<=n;i++) {
  45. dp1[i]=query(c[i]-); dp1[i].len++;
  46. update(c[i],dp1[i]);
  47. upd(ans,dp1[i]);
  48. }
  49.  
  50. for (int i=;i<=n;i++) c[i]=m-c[i]+;
  51. memset(bit,,sizeof(bit));
  52. for (int i=n;i;i--) {
  53. dp2[i]=query(c[i]-); dp2[i].len++;
  54. update(c[i],dp2[i]);
  55. }
  56.  
  57. for (int i=;i<=n;i++)
  58. if (dp1[i].len+dp2[i].len-==ans.len) {
  59. int t=(LL)dp1[i].sum*dp2[i].sum%P*power(ans.sum,P-)%P;
  60. printf("%d ",t);
  61. } else printf("0 ");
  62. return ;
  63. }

最长上升子序列(LIS)长度及其数量的更多相关文章

  1. 最长上升子序列(LIS)长度的O(nlogn)算法

    最长上升子序列(LIS)的典型变形,熟悉的n^2的动归会超时.LIS问题可以优化为nlogn的算法.定义d[k]:长度为k的上升子序列的最末元素,若有多个长度为k的上升子序列,则记录最小的那个最末元素 ...

  2. 动态规划--最长上升子序列(LIS)的长度

    l例如:对于[3,1,4,2,5],最长上升子序列的长度是3 arr = [3,1,4,5,9,2,6,5,0] def lis(arr): #dp[i]表示第i个位置的值为尾的数组的最长递增子序列的 ...

  3. 专题2:最长上升子序列LIS

        A HDU 1025 Constructing Roads In JGShining's Kingdom     B POJ 3903 Stock Exchange     C OpenJ_B ...

  4. 一个数组求其最长递增子序列(LIS)

    一个数组求其最长递增子序列(LIS) 例如数组{3, 1, 4, 2, 3, 9, 4, 6}的LIS是{1, 2, 3, 4, 6},长度为5,假设数组长度为N,求数组的LIS的长度, 需要一个额外 ...

  5. 最长上升子序列(LIS)模板

    最长递增(上升)子序列问题:在一列数中寻找一些数,这些数满足:任意两个数a[i]和a[j],若i<j,必有a[i]<a[j],这样最长的子序列称为最长递增(上升)子序列. 考虑两个数a[x ...

  6. hdu1025 dp(最长上升子序列LIS)

    题意:有一些穷国和一些富国分别排在两条直线上,每个穷国和一个富国之间可以建道路,但是路不能交叉,给出每个穷国和富国的联系,求最多能建多少条路 我一开始在想有点像二分图匹配orz,很快就发现,当我把穷国 ...

  7. SGU 199 - Beautiful People 最长上升子序列LIS

    要邀请n个人参加party,每个人有力量值strength Si和魅力值 beauty Bi,如果存在两人S i ≤ S j and B i ≥ B j 或者  S i ≥ S j and B i ≤ ...

  8. 最长上升子序列 LIS(Longest Increasing Subsequence)

    引出: 问题描述:给出一个序列a1,a2,a3,a4,a5,a6,a7….an,求它的一个子序列(设为s1,s2,…sn),使得这个子序列满足这样的性质,s1<s2<s3<…< ...

  9. 【CJOJ2498】【DP合集】最长上升子序列 LIS

    题面 Description 给出一个 1 ∼ n (n ≤ 10^5) 的排列 P 求其最长上升子序列长度 Input 第一行一个正整数n,表示序列中整数个数: 第二行是空格隔开的n个整数组成的序列 ...

随机推荐

  1. UVa1636 Headshot 【迁移自洛谷博客】

    说明:小蒟蒻hkk现在正在做一些概率的题目,由于这方面和数学还有点关系,所以需要一些数学的思维,也需要表述出来,如夏军所述"把自己给讲懂",所以写了些blog,主要为帮助自己理解. ...

  2. 【leetcode】1025. Divisor Game

    题目如下: Alice and Bob take turns playing a game, with Alice starting first. Initially, there is a numb ...

  3. 【leetcode】1018. Binary Prefix Divisible By 5

    题目如下: Given an array A of 0s and 1s, consider N_i: the i-th subarray from A[0] to A[i] interpreted a ...

  4. CSU 1503: 点到圆弧的距离(计算几何)

    题目描述 输入一个点 P 和一条圆弧(圆周的一部分),你的任务是计算 P 到圆弧的最短距离.换句话 说,你需要在圆弧上找一个点,到 P点的距离最小. 提示:请尽量使用精确算法.相比之下,近似算法更难通 ...

  5. windows系统的安装时间怎么查看

    方法一:利用命令符窗口查询 直接按下Windows+R组合键  出现运行对话框(或 点击开始—运行),输入cmd,进入命令符窗口 然后,在该界面下输入”systeminfo”,然后回车,等待系统自动运 ...

  6. 多列布局(column)

    容器的属性 column-width: auto | < length > .给列定义一个最小宽度(min-width). auto: 列宽由其他元素决定. length: 显式设置最小宽 ...

  7. 8051 r0-r7 是什么

    R0~R7表示当前选中的寄存器组的寄存器0~7,5I机有p0,p1,p2,p3口,每组有八个寄存器(R0-R7),比如MOV A Rn (n=0~7),表示把寄存器Rn 的内容送给累加器A,其中源操作 ...

  8. 测开之路五十七:实现runner和测试报告

    准备测试用例 from fox.case import Casefrom src import Calculator class TestCalculator(Case): def setUp(sel ...

  9. UITableView 支持左右滑动(一)

    原理如下: SwipeTableView subView 1 :  UIScrollView作为容器, 主要负责左右滑动, 每个tableView的顶部设置相同的contentInset subVie ...

  10. 将js/css脚本放到png图片中的实践。

     http://blog.csdn.net/zswang/article/details/7061560 将js/css脚本放到png图片中的实践. 标签: 脚本functionxmlhttprequ ...