子序列问题

最长上升子序列(LIS)

南昌理工学院ps:(这周划水严重,就不发学习总结了)

最长上升子序列LIS)

LIS(最长上升子序列)(本蒟蒻只会用想理解点这
n*n模板

//定义a[n]为要求的序列
for (int i = 1; i <= n; ++i) {
  na = 0;
  for (int j = 1; j <= i; ++j) {//寻找到a[i]的最长子序列长度
   if (a[i] > a[j])
    na = max(na, dp[j]);
  }
  dp[i] = na + 1;
 }

nlogn模板
用二分的方法进行查找比较,节省时间

//查找a[n],
int len = 1;
memset(d, 127, sizeof(d));
 d[len] = a[1];
 for (int i = 2; i <= n; ++i){
  if (d[len] >= a[i]) d[++len] = a[i];
  else {
   int l = 0, r = len;
   while (l < r) {
    int mid = (l + r) >> 1;
    if (d[mid] >= a[i]) l = mid + 1;
    else r = mid;
   }
   d[l] = a[i];
  }
 }
 cout << len << endl;

LIS(n *n)经典例题合唱队形
解法:先正着求一遍LIS再反求一遍LIS,取最大值 - 1即可(因为最大值算了两遍)。
举例:
输入 8
186 186 150 200 160 130 197 220
正求LIS 1 1 1 2 1 1 2 3
反求LIS 2 2 1 3 2 1 1 1
max 3 3 2 5 3 2 3 4
AC代码

#include<iostream>
#include<algorithm>
using namespace std;
int n, ans = 0, na;
int dp[101], f[101];
int a[101];
int main()
{
 cin >> n;
 for (int i = 1; i <= n; ++i) {
  cin >> a[i];
 }
 for (int i = 1; i <= n; ++i) {
  na = 0;
  for (int j = 1; j <= i; ++j) {
   if (a[i] > a[j])
    na = max(na, dp[j]);
  }
  dp[i] = na + 1;
 }
 for (int i = n; i > 0; --i) {
  na = 0;
  for (int j = n; j >= i; --j) {
   if (a[i] > a[j])
    na = max(na, f[j]);
  }
  f[i] = na + 1;
 }
 for (int i = 1; i <= n; ++i)
 {
  ans = max(ans, f[i] + dp[i]);
 }
 
 cout << n - ans + 1 << endl;
 return 0;
}

LIS(nlogn)例题 导弹拦截
解法:
第一问正着用LIS(nlogn)求一遍最长下降子序列。
第二问正着用LIS(nlogn)求一遍最长上升子序列的长度(因为有一个递增的话,就要多一台。)不理解的可以手推或者去看看狄尔沃斯定理看了可能会… )。
ps:因为数据过大,用 n* n 的求LIS只能过一半(100分),所以要用nlogn(200分)。
AC代码

#include<iostream>
#include<cstring>
using namespace std;
int a[1000001], d[100001];
int u, n = 0, ans = 0, len = 1;
int main()
{
	while (cin >> u)
	{
		a[++n] = u;
	}
	memset(d, 127, sizeof(d));
	d[len] = a[1];
	for (int i = 2; i <= n; ++i){
		if (d[len] >= a[i]) d[++len] = a[i];
		else {
			int l = 0, r = len;
			while (l < r) {
				int mid = (l + r) >> 1;
				if (d[mid] >= a[i]) l = mid + 1;
				else r = mid;
			}
			d[l] = a[i];
		}
	}
	cout << len << endl;
	len = 1;
	memset(d, -1, sizeof(d));
	d[1] = a[1];
	for (int i = 2; i <= n; ++i) {
		if (d[len] < a[i]) d[++len] = a[i];
		else {
			int l = 0, r = len;
			while (l < r) {
				int mid = (l + r) >> 1;
				if (d[mid] >= a[i]) r = mid;
				else l = mid + 1;
			}
			d[l] = a[i];
		}
	}
	cout << len << endl;
	return 0;
}

LCS(最长公共子序列)

解释
n*n模板

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1000;
char a[N],b[N];
int dp[N][N];
int main()
{
    int lena,lenb,i,j;
    while(scanf("%s%s",a,b)!=EOF)
    {
        memset(dp,0,sizeof(dp));
        lena=strlen(a);
        lenb=strlen(b);
        for(i=1;i<=lena;i++)
        {
            for(j=1;j<=lenb;j++)
            {
                if(a[i-1]==b[j-1])
                {
                    dp[i][j]=dp[i-1][j-1]+1;
                }
                else
                {
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        printf("%d\n",dp[lena][lenb]);
    }
    return 0;
}

nlogn

  1. 转化:将LCS问题转化成LIS问题。
    假设有两个序列 s1[ 1~6 ] = { a, b, c , a, d, c }, s2[ 1~7 ] = { c, a, b, e, d, a, b }。
    记录s1中每个元素在s2中出现的位置, 再将位置按降序排列, 则上面的例子可表示为:
    loc( a)= { 6, 2 }, loc( b ) = { 7, 3 }, loc( c ) = { 1 }, loc( d ) = { 5 }。
    将s1中每个元素的位置按s1中元素的顺序排列成一个序列s3 = { 6, 2, 7, 3, 1, 6, 2, 5, 1 }。
    在对s3求LIS得到的值即为求LCS的答案。(出处
    例题回文子串
    思路:把得到的序列a倒序处理一下,得到一个新序列b.
    求一下a, b的最长公共子序列的长度n,输出a序列长度 - n.
    AC代码
#include<iostream>
#include<string>
using namespace std;
string a, b;
int dp[1001][1001];
int len1, len2;
void lcs(int i,int j)

{

    for(i=1; i<=len1; i++)
         
    {

        for(j=1; j<=len2; j++)

        {

            if(a[i-1] == b[j-1])

                dp[i][j] = dp[i-1][j-1] + 1;

            else if(dp[i-1][j] > dp[i][j-1])

                dp[i][j] = dp[i-1][j];

            else

                dp[i][j] = dp[i][j-1];

        }

    }

}
int main()
{
    cin >> a;
    len1 = len2 = a.size();
    for (int i = len1 - 1; i >= 0; --i)
    {
        b += a[i];
    }
    lcs(len1, len2);
    cout << len1 - dp[len1][len2];
    return 0;
}

nlogn 例题模板题

AC代码
#include<iostream>
#include<cstdio>
using namespace std;
int a[100001], b[100001], map[100001], f[100001];
int n, len = 0;
int read() {
	int x = 0, f = 1; char ch = getchar();
	while (ch < '0' || ch>'9') { if (ch == '-')f = -1; ch = getchar(); }
	while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
	return x * f;
}

int main()
{
	cin >> n;
	for (int i = 1; i <= n; ++i) a[i] = read(), map[a[i]] = i;
	for (int i = 1; i <= n; ++i) b[i] = read(), f[i] = 0x7fffffff;
	for (int i = 1; i <= n; ++i) {
		if (map[b[i]] > f[len]) f[++len] = map[b[i]];
		else {
			int l = 0, r = len, mid;
			while (l < r) {
				mid = (l + r) / 2;
				if (f[mid] > map[b[i]]) r = mid;
				else l = mid + 1;
			}f[l] = min(map[b[i]], f[l]);
		}
	}
	cout << len;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值