【二分】【动态规划】Gym - 101156E - Longest Increasing Subsequences

最长上升子序列算法
本文介绍了一种求解最长上升子序列(LIS)及其方案数的高效算法,并提供了两种实现方式,一种是O(n^2)的时间复杂度,另一种则是采用二分查找结合前缀和的nlogn算法。

求最长上升子序列方案数。

转载自:http://blog.youkuaiyun.com/u013445530/article/details/47958617,如造成不便,请博主联系我。

数组A包含N个整数(可能包含相同的值)。设S为A的子序列且S中的元素是递增的,则S为A的递增子序列。如果S的长度是所有递增子序列中最长的,则称S为A的最长递增子序列(LIS)。A的LIS可能有很多个。例如A为:{1 3 2 0 4},1 3 4,1 2 4均为A的LIS。给出数组A,求A的LIS有多少个。由于数量很大,输出Mod 1000000007的结果即可。相同的数字在不同的位置,算作不同的,例如 {1 1 2} 答案为2。 
Input 
第1行:1个数N,表示数组的长度。(1 <= N <= 50000) 
第2 - N + 1行:每行1个数A[i],表示数组的元素(0 <= A[i] <= 10^9) 
Output 
输出最长递增子序列的数量Mod 1000000007。 
Input示例 






Output示例 
2

必须用nlogn算法,否则超时,那么我们如何计算LIS的个数呢?

先开始我想到的是o(n^2)的做法,很容易理解

#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std; const int M = 500000+100; int a[M]; int c[M]; int dp[M]; long long cent[M]; int INF = 1e9 + 1000; const int mod =1000000007; int input() { int ans=0; char a; while((a=getchar())<'0'||a>'9'); ans=a-'0'; while((a=getchar())>='0'&&a<='9') { ans=ans*10+a-'0'; } return ans; } int main() { int n; #ifdef xxz freopen("in.txt","r",stdin); #endif // xxz while(~scanf("%d",&n)) { for(int i = 0; i < n; i++) a[i] = input() , cent[i] = 1; int Max = 0; fill(dp,dp+n,0); long long ans = 0; for(int i = 0; i < n; i++) { dp[i] = 1; for(int j = 0; j < i; j++) { if(a[j] < a[i]) { if(dp[i] < dp[j] + 1) { dp[i] = dp[j] + 1; cent[i] = cent[j]; } else if(dp[i] == dp[j] + 1) cent[i] = (cent[i] +cent[j])%mod; } } Max = max(Max,dp[i]); } for(int i = 0; i < n; i++) { if(dp[i] == Max) ans = (ans + cent[i]) % mod; } printf("%d\n",ans%mod); } return 0; }

然后从网上搜nlogn的算法没搜到,然后问了好多大神,九爷,鸟神,rabbit,都说用线段树或者树状数组搞,好吧,没搞出来。

然后问tyh,他搜到了一篇国外高手写的思路,看完以后直接转换为代码 
二分+前缀和,orz….膜拜田博士…….. 
果然搜索姿势要正确呀 
思路地址: 
http://stackoverflow.com/questions/22923646/number-of-all-longest-increasing-subsequences

我用中文解释下: 
就是取二元组(i,j),i表示以i元素结尾的序列,j表示方案数 
比如: 
add 1 
len1: (1,1);

add 2:

len1(1,1); 
len2(2,1);

add 5 
len1 (1,1); 
len2 (2,1); 
len3 (5,1);

add 4 
len1 (1,1); 
len2 (2,1); 
len3 (5,1) (4,1); 
……

我们可以找到规律,就是没一行j都是从达到小减少 
新插入一个数,我们先找它应该处于哪一行,用 
就是用LIS的nlogn算法找,它的方案数就等于它上一行比这个数小的所有方案和


#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath> #include <cstdlib> using namespace std; typedef long long LL; const int MOD = 1e9 + 7; const int INF = 0x7fffffff; const int N = 50000 + 10; vector <int> val[N]; // val[i]: 最大长度为i+1的序列的最后一个元素组成的序列 vector <int> sum[N]; // sum[i]: 对应val中每个序列数量的组成的前缀和。 vector <int> last(N, INF); // last[i]: val[i].back() int input() { int ans=0; char a; while((a=getchar())<'0'||a>'9'); ans=a-'0'; while((a=getchar())>='0'&&a<='9') { ans=ans*10+a-'0'; } return ans; } void add(int x, int len, int v) { val[len].push_back(x); if(sum[len].size() == 0) { sum[len].push_back(v); } else { sum[len].push_back((sum[len].back() + v) % MOD); } last[len] = x; } int main() { int n, x; while (scanf("%d", &n) != EOF) { int Max = 0; for(int i = 0; i < n; i++) { x = input(); int len = lower_bound(last.begin(), last.end(), x) - last.begin(); Max = max(Max, len); if(len == 0) { add(x, len, 1); } else { int pos = upper_bound(val[len - 1].begin(), val[len - 1].end(), x,greater<int>() ) - val[len - 1].begin(); int cnt; if(pos == 0) { cnt = sum[len - 1].back(); } else { cnt = (sum[len - 1].back() - sum[len - 1][pos - 1] + MOD) % MOD; } add(x, len, cnt); } } printf("%d\n", sum[Max].back()); } return 0; }

转载于:https://www.cnblogs.com/autsky-jadek/p/8343052.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值