最长上升子序列(LIS)长度及其数量
例题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]=返回结果数量。代码实现是学习上面博客大佬,带有一些小技巧。
- #include<bits/stdc++.h>
- using namespace std;
- const int N=5e4+;
- const int P=1e9+;
- int n,m,a[N],b[N];
- struct dat{
- int len,sum;
- }bit[N],dp[N];
- void upd(dat &x,dat y) { //计算y对x的影响
- if (x.len>y.len) return;
- else if (x.len<y.len) x=y;
- else x.sum+=y.sum,x.sum%=P;
- }
- void update(int x,dat v) {
- for (;x<=m;x+=x&-x) upd(bit[x],v);
- }
- dat query(int x) {
- dat ret=(dat){,};
- for (;x;x-=x&-x) upd(ret,bit[x]);
- return ret;
- }
- int main()
- {
- cin>>n;
- for (int i=;i<=n;i++) scanf("%d",&a[i]),b[i]=a[i];
- sort(b+,b+n+);
- m=unique(b+,b+n+)-(b+);
- for (int i=;i<=n;i++) a[i]=lower_bound(b+,b+m+,a[i])-b;
- //update(0,(dat){0,1}); 不能这样写会死循环
- for (int i=;i<=n;i++) {
- dp[i]=query(a[i]-); dp[i].len++;
- update(a[i],dp[i]);
- }
- dat ans=(dat){,};
- for (int i=;i<=n;i++) upd(ans,dp[i]);
- cout<<ans.sum<<endl;
- return ;
- }
然后是同一道题的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]总和。
- #include<bits/stdc++.h>
- using namespace std;
- const int N=5e4+;
- const int P=1e9+;
- const int INF=0x3f3f3f3f;
- int n,m,a[N],g[N],f[N],s[N];
- vector<int> len[N],sum[N]; //len[i]存放LIS为i的数a[i],sum[i]存放a[i]对应的LIS数量
- //显然len[i]的元素是单调不增的
- int Find(int p,int x) { //二分len=p的最后一个>=x的位置,这个位置后面的就是答案
- int l=,r=len[p].size()-,mid;
- while (l<r) {
- mid=l+r+>>;
- if (len[p][mid]>=x) l=mid;
- else r=mid-;
- }
- return ((sum[p].back()-sum[p][l])%P+P)%P;
- }
- int main()
- {
- cin>>n;
- for (int i=;i<=n;i++) scanf("%d",&a[i]);
- memset(g,0x3f,sizeof(g));
- for (int i=;i<=n;i++) len[i].push_back(INF),sum[i].push_back();
- len[].push_back(-INF),sum[].push_back();
- int Max=,ans=;
- for (int i=;i<=n;i++) {
- int t=lower_bound(g+,g+n+,a[i])-g;
- g[t]=a[i];
- f[i]=t; s[i]=Find(t-,a[i]);
- len[t].push_back(a[i]); sum[t].push_back((sum[t].back()+s[i])%P);
- if (f[i]>Max) Max=f[i],ans=s[i];
- else if (f[i]==Max) ans=(ans+s[i])%P;
- }
- cout<<ans<<endl;
- return ;
- }
题目练习:
2019计蒜之道 复赛 外教 Michale 变身大熊猫:https://nanti.jisuanke.com/t/39611
容易想到a[i]会在LIS的概率就是:(必须经过a[i]的LIS数量)/(所有的LIS数量) 。所有的LIS数量好求,主要问题就是怎么求必须经过a[i]的LIS的数量,这个问题也不难想,就是 从左到右以a[i]结尾的LIS数量*从右到左为a[i]为结尾的最长下降子序列数量。这就用到上面的知识了。
- #include<bits/stdc++.h>
- using namespace std;
- const int P=;
- const int N=5e5+;
- typedef long long LL;
- int n,m,a[N],b[N],c[N];
- struct dat{
- int len,sum;
- }dp1[N],dp2[N],bit[N];
- void upd(dat &x,dat y) {
- if (x.len>y.len) return;
- else if (x.len<y.len) x=y;
- else x.sum+=y.sum,x.sum%=P;
- }
- void update(int x,dat v) {
- for (;x<=m;x+=x&-x) upd(bit[x],v);
- }
- dat query(int x) {
- dat ret=(dat){,};
- for (;x;x-=x&-x) upd(ret,bit[x]);
- return ret;
- }
- int power(int x,int p) {
- int ret=;
- for (;p;p>>=) {
- if (p&) ret=((LL)ret*x)%P;
- x=((LL)x*x)%P;
- }
- return ret;
- }
- int main()
- {
- cin>>n;
- for (int i=;i<=n;i++) scanf("%d",&a[i]),b[i]=a[i];
- sort(b+,b+n+);
- m=unique(b+,b+n+)-(b+);
- for (int i=;i<=n;i++) c[i]=lower_bound(b+,b+m+,a[i])-b;
- dat ans=(dat){,};
- memset(bit,,sizeof(bit));
- for (int i=;i<=n;i++) {
- dp1[i]=query(c[i]-); dp1[i].len++;
- update(c[i],dp1[i]);
- upd(ans,dp1[i]);
- }
- for (int i=;i<=n;i++) c[i]=m-c[i]+;
- memset(bit,,sizeof(bit));
- for (int i=n;i;i--) {
- dp2[i]=query(c[i]-); dp2[i].len++;
- update(c[i],dp2[i]);
- }
- for (int i=;i<=n;i++)
- if (dp1[i].len+dp2[i].len-==ans.len) {
- int t=(LL)dp1[i].sum*dp2[i].sum%P*power(ans.sum,P-)%P;
- printf("%d ",t);
- } else printf("0 ");
- return ;
- }
最长上升子序列(LIS)长度及其数量的更多相关文章
- 最长上升子序列(LIS)长度的O(nlogn)算法
最长上升子序列(LIS)的典型变形,熟悉的n^2的动归会超时.LIS问题可以优化为nlogn的算法.定义d[k]:长度为k的上升子序列的最末元素,若有多个长度为k的上升子序列,则记录最小的那个最末元素 ...
- 动态规划--最长上升子序列(LIS)的长度
l例如:对于[3,1,4,2,5],最长上升子序列的长度是3 arr = [3,1,4,5,9,2,6,5,0] def lis(arr): #dp[i]表示第i个位置的值为尾的数组的最长递增子序列的 ...
- 专题2:最长上升子序列LIS
A HDU 1025 Constructing Roads In JGShining's Kingdom B POJ 3903 Stock Exchange C OpenJ_B ...
- 一个数组求其最长递增子序列(LIS)
一个数组求其最长递增子序列(LIS) 例如数组{3, 1, 4, 2, 3, 9, 4, 6}的LIS是{1, 2, 3, 4, 6},长度为5,假设数组长度为N,求数组的LIS的长度, 需要一个额外 ...
- 最长上升子序列(LIS)模板
最长递增(上升)子序列问题:在一列数中寻找一些数,这些数满足:任意两个数a[i]和a[j],若i<j,必有a[i]<a[j],这样最长的子序列称为最长递增(上升)子序列. 考虑两个数a[x ...
- hdu1025 dp(最长上升子序列LIS)
题意:有一些穷国和一些富国分别排在两条直线上,每个穷国和一个富国之间可以建道路,但是路不能交叉,给出每个穷国和富国的联系,求最多能建多少条路 我一开始在想有点像二分图匹配orz,很快就发现,当我把穷国 ...
- 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 ≤ ...
- 最长上升子序列 LIS(Longest Increasing Subsequence)
引出: 问题描述:给出一个序列a1,a2,a3,a4,a5,a6,a7….an,求它的一个子序列(设为s1,s2,…sn),使得这个子序列满足这样的性质,s1<s2<s3<…< ...
- 【CJOJ2498】【DP合集】最长上升子序列 LIS
题面 Description 给出一个 1 ∼ n (n ≤ 10^5) 的排列 P 求其最长上升子序列长度 Input 第一行一个正整数n,表示序列中整数个数: 第二行是空格隔开的n个整数组成的序列 ...
随机推荐
- UVa1636 Headshot 【迁移自洛谷博客】
说明:小蒟蒻hkk现在正在做一些概率的题目,由于这方面和数学还有点关系,所以需要一些数学的思维,也需要表述出来,如夏军所述"把自己给讲懂",所以写了些blog,主要为帮助自己理解. ...
- 【leetcode】1025. Divisor Game
题目如下: Alice and Bob take turns playing a game, with Alice starting first. Initially, there is a numb ...
- 【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 ...
- CSU 1503: 点到圆弧的距离(计算几何)
题目描述 输入一个点 P 和一条圆弧(圆周的一部分),你的任务是计算 P 到圆弧的最短距离.换句话 说,你需要在圆弧上找一个点,到 P点的距离最小. 提示:请尽量使用精确算法.相比之下,近似算法更难通 ...
- windows系统的安装时间怎么查看
方法一:利用命令符窗口查询 直接按下Windows+R组合键 出现运行对话框(或 点击开始—运行),输入cmd,进入命令符窗口 然后,在该界面下输入”systeminfo”,然后回车,等待系统自动运 ...
- 多列布局(column)
容器的属性 column-width: auto | < length > .给列定义一个最小宽度(min-width). auto: 列宽由其他元素决定. length: 显式设置最小宽 ...
- 8051 r0-r7 是什么
R0~R7表示当前选中的寄存器组的寄存器0~7,5I机有p0,p1,p2,p3口,每组有八个寄存器(R0-R7),比如MOV A Rn (n=0~7),表示把寄存器Rn 的内容送给累加器A,其中源操作 ...
- 测开之路五十七:实现runner和测试报告
准备测试用例 from fox.case import Casefrom src import Calculator class TestCalculator(Case): def setUp(sel ...
- UITableView 支持左右滑动(一)
原理如下: SwipeTableView subView 1 : UIScrollView作为容器, 主要负责左右滑动, 每个tableView的顶部设置相同的contentInset subVie ...
- 将js/css脚本放到png图片中的实践。
http://blog.csdn.net/zswang/article/details/7061560 将js/css脚本放到png图片中的实践. 标签: 脚本functionxmlhttprequ ...