Time Limit: 1000MS | Memory Limit: 10000K | |
Total Submissions: 14521 | Accepted: 7850 |
Description
A typical situation is schematically depicted in figure 1. The ports of the two functional blocks are numbered from 1 to p, from top to bottom. The signal mapping is described by a permutation of the numbers 1 to p in the form of a list of p unique numbers in the range 1 to p, in which the i:th number specifies which port on the right side should be connected to the i:th port on the left side.Two signals cross if and only if the straight lines connecting the two ports of each pair do.
Input
Output
Sample Input
4 6 4 2 6 3 1 5 10 2 3 4 5 6 7 8 9 10 1 8 8 7 6 5 4 3 2 1 9 5 8 9 2 3 1 7 4 6
Sample Output
3 9 1 4
Source
题意:求上升公共子序列
先介绍一种O(n*n)算法:
我们定义dp[i]: 以a[i]为末尾的最长上升子序列的长度
以a[i]结尾的上升子序列是
1 只包含a[i]的子序列
2 在满足j<i并且a[j]<a[i]的以a[j]为结尾的上升子序列末尾,追加上a[i]后得到的子序列
状态转移方程:dp[i]=max(dp[i],dp[j]+1)(当然该方程是有前提条件的)
然而由于数据过大,这样写会tle
tle 代码:
#include<iostream>
using namespace std;
int t;
int n;
int a[40000];
int dp[40000];
int ma;
int ans;
int main()
{cin>>t;while(t--){cin>>n;for(int i=1;i<=n;i++)cin>>a[i];for(int i=1;i<=n;i++)dp[i]=1;ans=0;for(int i=1;i<=n;i++){for(int j=1;j<i;j++){if(a[j]<a[i])dp[i]=max(dp[i],dp[j]+1);}ans=max(ans,dp[i]);}cout<<ans<<endl;}return 0;
}
那我们要用O(logn)复杂度的算法来解决这道题
我们定义dp[i]:长度为i的上升子序列中末尾元素的最小值(不存在的话就是inf)
原理:如果子序列的长度相同,那么最末尾的元素较小的在之后会有优势。整个过程dp[i]是有序的,因此我们就可以采用二分查找来解决,那么时间复杂度就优化为nlogn。
我们举个例子来模拟一下整个过程;
假设存在一个序列d[1..9] = 2 1 5 3 6 4 8 9 7,可以看出来它的LIS长度为5。n
下面一步一步试着找出它。
我们定义一个序列B,然后令 i = 1 to 9 逐个考察这个序列。
此外,我们用一个变量Len来记录现在最长算到多少了
首先,把d[1]有序地放到B里,令B[1] = 2,就是说当只有1一个数字2的时候,长度为1的LIS的最小末尾是2。这时Len=1
然后,把d[2]有序地放到B里,令B[1] = 1,就是说长度为1的LIS的最小末尾是1,d[1]=2已经没用了,很容易理解吧。这时Len=1
接着,d[3] = 5,d[3]>B[1],所以令B[1+1]=B[2]=d[3]=5,就是说长度为2的LIS的最小末尾是5,很容易理解吧。这时候B[1..2] = 1, 5,Len=2
再来,d[4] = 3,它正好加在1,5之间,放在1的位置显然不合适,因为1小于3,长度为1的LIS最小末尾应该是1,这样很容易推知,长度为2的LIS最小末尾是3,于是可以把5淘汰掉,这时候B[1..2] = 1, 3,Len = 2
继续,d[5] = 6,它在3后面,因为B[2] = 3, 而6在3后面,于是很容易可以推知B[3] = 6, 这时B[1..3] = 1, 3, 6,还是很容易理解吧? Len = 3 了噢。
第6个, d[6] = 4,你看它在3和6之间,于是我们就可以把6替换掉,得到B[3] = 4。B[1..3] = 1, 3, 4, Len继续等于3
第7个, d[7] = 8,它很大,比4大,嗯。于是B[4] = 8。Len变成4了
第8个, d[8] = 9,得到B[5] = 9,嗯。Len继续增大,到5了。
最后一个, d[9] = 7,它在B[3] = 4和B[4] = 8之间,所以我们知道,最新的B[4] =7,B[1..5] = 1, 3, 4, 7, 9,Len = 5。
于是我们知道了LIS的长度为5。
!!!!! 注意。这个1,3,4,7,9不是LIS,它只是存储的对应长度LIS的最小末尾。有了这个末尾,我们就可以一个一个地插入数据。虽然最后一个d[9] = 7更新进去对于这组数据没有什么意义,但是如果后面再出现两个数字 8 和 9,那么就可以把8更新到d[5], 9更新到d[6],得出LIS的长度为6。
那么使得dp[i]不为inf的最大的i即为最长上升子序列的长度(举例转自他人博客)
//该题要用c语言不然会超时
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define inf 0x3f3f3f
using namespace std;
int t;
int n;
int a[40000];
int dp[40000];
int main()
{scanf("%d",&t);while(t--){scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d",&a[i]);dp[i]=inf;//初始化为正无穷大}for(int i=1;i<=n;i++){*lower_bound(dp+1,dp+1+n,a[i])=a[i];//lower_bound返回一个非递减序列[first, last)中的第一个大于等于值val的位置。因此我们加上*就是该位置所储存的变量}printf("%d\n",lower_bound(dp+1,dp+n+1,inf)-dp-1);//最后一个不为inf的数的下标即为答案}
}
最长上升子序列还有一种o(n*n)的方法是采用排序+最长公共子序列,在此就不在详述。