您的位置:首页 > 其它

LCS/LIS/LCIS

2013-06-16 11:54 351 查看

LCS

这里仅仅记录一下LCS算法的思路:典型的DP,空间换时间

step 1: 先比较两个子串的对应位是否相同,如果相同,则b[i][j]='\',并且c[i][j]=c[i-1][j-1]+1,否则转向step2

step 2:如果上角的值>=左角的值,那么将上角的值赋给c[i][j],同时b[i][j]箭头指向大值(指向上角)。如果上角的值<左角的值,箭头指向大值(指向左),左角的值赋给c[i][j]



实例:

求X=<A,B,C,B,D,A,B>和Y=<B,D,C,A,B,A>的最长公共子序列,在第一行中,第一行为A和第4列第6列的值A相同,

于是b[][]中存储的箭头指向上对角。同时c[][]的值为上对角的值+1。如果X,Y对应的相应位不同,那么b[][]的箭头总是

指向大值,如果两值相等,则指向上面那一个。遍历完之后,就可以得出b[][]和c[][],就能确定LCS。

要点:

1.b[i][j]存放的是箭头,若Xi==Yi,则箭头指向对角线b[i-1][j-1],否则箭头总是指向大值,

若上值b[i-1][j]和左值b[i][j-1]相等,优先指向上值。

2.c[i][j]存放的是数值,若Xi==Yi,则c[i][j]值为上对角值c[i-1][j-1]+1,否则c[i][j]=max(c[i][j-1],c[i-1][j])

代码:

#include<iostream>
using namespace std;
#define M 7
#define N 6
int b[M+1][N+1]={0};//保存箭头
int c[M+1][N+1]={0};//保存值

void Lcs_Length(char* X,char* Y)
{
int i,j;
for(i=1;i<=M;i++)//第0列置0
c[i][0]=0;
for(j=0;j<=N;j++)
c[0][j]=0;  //第0行置0
for(i=1;i<=M;i++)
{
for(j=1;j<=N;j++)
{
if(X[i]==Y[j])//比较两个字串对应位,如果相等,则=对角+1
{
c[i][j]=c[i-1][j-1]+1;
b[i][j]=1;//1代表↖ 
}
else if(c[i-1][j]>=c[i][j-1])//如果上面值>=下面值
{
c[i][j]=c[i-1][j];//c[i][j]=大值
b[i][j]=2;//2代表↑
}
else
{
c[i][j]=c[i][j-1];//赋的总是大值,箭头总是指向大值
b[i][j]=3;//3代表←
}
}
}
}
void Print_Lcs(char* X,int i,int j)
{
if(i==0 || j==0)
return ;
if(b[i][j]==1)
{
Print_Lcs(X,i-1,j-1);
cout<<X[i]<<' ';//只需输出↖对应的值
}
else if(b[i][j]==2)
Print_Lcs(X,i-1,j);
else Print_Lcs(X,i,j-1);
}
void main()
{
char  X[M+1] = {'0','A','B','C','B','D','A','B'};
char  Y[N+1] = {'0','B','D','C','A','B','A'};
Lcs_Length(X, Y);
Print_Lcs(X, M, N);
cout<<endl;
for(int i=0;i<=M;i++)
{
for(int j=0;j<=N;j++)
{
cout<<c[i][j]<<' ';
}
cout<<endl;
}
cout<<endl;
for(int i=0;i<=M;i++)
{
for(int j=0;j<=N;j++)
{
switch(b[i][j])
{
case 0:
{
cout<<b[i][j]<<"  ";
break;
}
case 1:
{
cout<<"↖"<<' ';
break;
}
case 2:
{
cout<<"↑"<<' ';
break;
}
case 3:
{
cout<<"←"<<' ';
break;
}
}
}
cout<<endl;
}
}

LCIS

以下来自百度文库:

最长公共上升子序列(LCIS)的O(n^2)算法

预备知识:动态规划的基本思想,LCS,LIS。

问题:字符串a,字符串b,求a和b的LCIS(最长公共上升子序列)。

首先我们可以看到,这个问题具有相当多的重叠子问题。于是我们想到用DP搞。DP的首要任务是什么?定义状态。

1定义状态F[i][j]表示以a串的前i个字符b串的前j个字符且以b[j]为结尾构成的LCIS的长度。

为什么是这个而不是其他的状态定义?最重要的原因是我只会这个,还有一个原因是我知道这个定义能搞到平方的算

法。而我这只会这个的原因是,这个状态定义实在是太好用了。这一点我后面再说。

我们来考察一下这个这个状态。思考这个状态能转移到哪些状态似乎有些棘手,如果把思路逆转一下,考察这个状态

的最优值依赖于哪些状态,就容易许多了。这个状态依赖于哪些状态呢?

首先,在a[i]!=b[j]的时候有F[i][j]=F[i-1][j]。为什么呢?因为F[i][j]是以b[j]为结尾的LCIS,如果F[i][j]>0那么就说明

a[1]..a[i]中必然有一个字符a[k]等于b[j](如果F[i][j]等于0呢?那赋值与否都没有什么影响了)。因为a[k]!=a[i],那么

a[i]对F[i][j]没有贡献,于是我们不考虑它照样能得出F[i][j]的最优值。所以在a[i]!=b[j]的情况下必然有F[i][j]=F[i-1][j]。这

一点参考LCS的处理方法。那如果a[i]==b[j]呢?首先,这个等于起码保证了长度为1的LCIS。然后我们还需要去找一

个最长的且能让b[j]接在其末尾的LCIS。之前最长的LCIS在哪呢?

首先我们要去找的F数组的第一维必然是i-1。因为i已经拿去和b[j]配对去了,不能用了。并且也不能是i-2,因为i-1必

然比i-2更优。第二维呢?那就需要枚举b[1]..b[j-1]了,因为你不知道这里面哪个最长且哪个小于b[j]。这里还有一个问

题,可不可能不配对呢?也就是在a[i]==b[j]的情况下,需不需要考虑F[i][j]=F[i-1][j]的决策呢?答案是不需要。因为如

果b[j]不和a[i]配对,那就是和之前的a[1]..a[j-1]配

对(假设F[i-1][j]>0,等于0不考虑),这样必然没有和a[i]配对优越。(为什么必然呢?因为b[j]和a[i]配对之后的转移

是max(F[i-1][k])+1,而和之前的i`配对则是max(F[i`-1][k])+1。显然有F[i][j]>F[i`][j],i`>i)

于是我们得出了状态转移方程

a[i]!=b[j]:   F[i][j]=F[i-1][j]

a[i]==b[j]:   F[i][j]=max(F[i-1][k])+1,1<=k<=j-1&&b[j]>b[k]

不难看到,这是一个时间复杂度为O(n^3)的DP,离平方还有一段距离。

但是,这个算法最关键的是,如果按照一个合理的递推顺序,max(F[i-1][k])的值我们可以在之前访问F[i][k]的时候通过

维护更新一个max变量得到。怎么得到呢?首先递推的顺序必须是状态的第一维在外层循环,第二维在内层循环。也

就是算好了F[1][len(b)]再去算F[2][1]。如果按照这个递推顺序我们可以在每次外层循环的开始加上令一个max变量为

0,然后开始内层循环。当a[i]>b[j]的时候令max=F[i-1][j]。如果循环到了a[i]==b[j]的时候,则令F[i][j]=max+1。最后答

案是F[len(a)][1]..F[len(a)][len(b)]的最大值。

参考代码:

#include<cstdio>
#include<cstring>
intf[1005][1005],a[1005],b[1005],i,j,t,n1,n2,max;
intmain()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n1,&n2);
for(i=1;i<=n1;i++)scanf("%d",&a[i]);
for(i=1;i<=n2;i++)scanf("%d",&b[i]);
memset(f,0,sizeof(f));
for(i=1;i<=n1;i++)
{
max=0;
for(j=1;j<=n2;j++)
{
f[i][j]=f[i-1][j];
if(a[i]>b[j]&&max<f[i-1][j])
max=f[i-1][j];
if (a[i]==b[j])
f[i][j]=max+1;
}
}
max=0;
for(i=1;i<=n2;i++)
if (max<f[n1][i])
max=f[n1][i];
printf("%d\n",max);
}
}


其实还有一个很风骚的一维的算法。在此基础上压掉了一维空间(时间还是平方)。i循环到x的时候,F[i]表示原来

F[x][j]。之所以可以这样,是因为如果a[i]!=b[j],因为F[x][j]=F[x-1][j]值不变,F[x]不用改变,沿用过去的就好了,和这

个比较维护更新得到的max值依然是我们要的。而a[i]==b[j]的时候,就改变F[x]的值好了。具体结合代码理解。

传送门HDU142

参考代码:

#include<iostream>
using namespace std;

//LIS[i][j]表示a[1-i]和b[1-j]之间的最长公共递增序列,并且最长公共递增序列以b[j]结尾
int LIS[505];

int a[505]; //数组a
int b[505]; //数组b

//LCIS模板
int LCIS(int n,int m)
{
int i,j,max;
memset(LIS,0,sizeof(LIS));
for(i=1; i<=n; ++i)
{
max = 0; //最大序列的长度
for(j=1; j<=m; ++j)
{
if( a[i] > b[j] && LIS[j] > max )
max = LIS[j];
else if( a[i] == b[j] )
{
if( max+1 > LIS[j] )
LIS[j] = max+1;
}
}
}
max = 0;
for(i=1; i<=m; ++i)
{
if( LIS[i] > max )
max = LIS[i];
}
return max;
}
int main()
{
int T,n,m,i;
cin >> T;
while( T-- )
{
cin >> n;
for(i=1; i<=n; ++i)
cin >> a[i];
cin >> m;
for(i=1; i<=m; ++i)
cin >> b[i];
cout << LCIS(n,m) << endl;
if( T )
cout << endl;
}
return 0;
}

LIS


Constructing Roads In JGShining's Kingdom

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)

Total Submission(s): 13369    Accepted Submission(s): 3808


Problem Description

JGShining's kingdom consists of 2n(n is no more than 500,000) small cities which are located in two parallel lines.

Half of these cities are rich in resource (we call them rich cities) while the others are short of resource (we call them poor cities). Each poor city is short of exactly one kind of resource and also each rich city is rich in exactly one kind of resource.
You may assume no two poor cities are short of one same kind of resource and no two rich cities are rich in one same kind of resource. 

With the development of industry, poor cities wanna import resource from rich ones. The roads existed are so small that they're unable to ensure the heavy trucks, so new roads should be built. The poor cities strongly BS each other, so are the rich ones. Poor
cities don't wanna build a road with other poor ones, and rich ones also can't abide sharing an end of road with other rich ones. Because of economic benefit, any rich city will be willing to export resource to any poor one.

Rich citis marked from 1 to n are located in Line I and poor ones marked from 1 to n are located in Line II. 

The location of Rich City 1 is on the left of all other cities, Rich City 2 is on the left of all other cities excluding Rich City 1, Rich City 3 is on the right of Rich City 1 and Rich City 2 but on the left of all other cities ... And so as the poor ones. 

But as you know, two crossed roads may cause a lot of traffic accident so JGShining has established a law to forbid constructing crossed roads.

For example, the roads in Figure I are forbidden.



In order to build as many roads as possible, the young and handsome king of the kingdom - JGShining needs your help, please help him. ^_^

 

Input

Each test case will begin with a line containing an integer n(1 ≤ n ≤ 500,000). Then n lines follow. Each line contains two integers p and r which represents that Poor City p needs to import resources from Rich City r. Process to
the end of file.

 

Output

For each test case, output the result in the form of sample. 

You should tell JGShining what's the maximal number of road(s) can be built. 

 

Sample Input

2
1 2
2 1
3
1 2
2 3
3 1

 

Sample Output

Case 1:
My king, at most 1 road can be built.

Case 2:
My king, at most 2 roads can be built.

Hint
Huge input, scanf is recommended.

 

Author

JGShining(极光炫影)

 

Recommend

We have carefully selected several similar problems for you:  1024 1074 1078 1080 1114 

 
这道题很好的应用了LIS的模板,这里讲一讲LIS的nlogn的思路,具体可以参照《编程之美》上的讲解。我们这里定义状态:
LIS[i]表示的是前i个元素的最长递增子序列的长度为LIS[i]且以arry[i]为最大值(arry[i]为结尾元素)
例如:arry:1 0 3 2,则有
LIS[0]=1 {1},
LIS[1]=1{1}或者{0}
LIS[2]=2{1,3}或者{0,3}
LIS[3]=2{1,2}或者{0,2}
同时定义:长度为i的递增子序列最大元素的最小值为MaxV[i]
最大元素的最小值听起来很绕口,还是以上面的例子说明1,0,3,2
长度为2的递增子序列有{1,3}{1,2}{0,3}{0,2},它们的最大元素分别是3,2,3,2,所以,最大元素的最小值是2,即MaxV[2]=2.长度为LIS[i]的递增子序列最大元素的最小值为MaxV[LIS[i]],维护了这此值,可以写出下面的LIS
#include<iostream>
using namespace std;

int MaxV[500005];
int arr[500005];

int BinarySearch(int* MaxV,int size,int x) //MaxV debug
{
int left = 0, right = size-1;
while(left <= right)
{
int mid = (left + right) / 2;
if(MaxV[mid] <= x)
{
left = mid + 1;
}else
{
right = mid - 1;
}
}
return left;
}

int LIS(int* A,int size)
{
MaxV[0] = A[0]; // 长度为1的递增子串的最大元素的最小值初始化为A[0]
int i,pos, nMaxLIS = 1;
for(i = 1; i < size; ++i) //因为数组是以0开始的,所以,这里的i=1表示的是第2个元素
{
if( A[i] > MaxV[nMaxLIS-1] ) //debug 点
MaxV[nMaxLIS++] = A[i];
else
{
pos = BinarySearch(MaxV,nMaxLIS,A[i]);
MaxV[pos] = A[i];
}
}
return nMaxLIS;
}

int main()
{
int n,poor,rich,i,Case = 1,road;
while(scanf("%d",&n)!=EOF)
{
for(i=0; i < n; ++i)
{
scanf("%d%d",&poor,&rich);
arr[poor-1] = rich;
}
road = LIS(arr,n);
if( road == 1 )
printf("Case %d:\nMy king, at most %d road can be built.\n\n",Case++,road);
else
printf("Case %d:\nMy king, at most %d roads can be built.\n\n",Case++,road);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息