​题目传送门​


UVA 111——History Grading【LCS最长公共子序列】_HDU


题意:

先给出nn个事件的正确发生时间顺序,在给出一些学生排出来的时间发生时间顺序,有两种得分方式:

在相应的时间点发生事件相同则得11分

可以得到的分数等于发生事件的相对时间顺序正确的最长长度。

求按照第二种方式可以获得的得分。

nn的范围,2<=n<=202<=n<=20


分析:

最长公共子序列的应用.之前已经有过一些相对应的练习,不过这道题关于历史时间的时间排序.其中输入的序列并不一定代表着时间的顺序,而是代表着事件i发生的位置在哪里.
注意,给出的序列和一般理解上的出现次序是不同的。比如4 2 3 1,一般理解是第4个事件在第1位,第2个事件在第2位,第1个事件在第4位。但在这道题目,序列的含义是第1个事件在第4位,第2个事件在第2位,第4个事件在第一位。为方便计算,需要对输入的序列进行转换,使数字对号入座。比如一个正确的序列是:

正确答案

学生答案

含义

输入的序列

3 1 2 4 9 5 10 6 8 7

2 10 1 3 8 4 9 5 7 6

历史事件发生在第几位

转换后的序列

2 3 1 4 6 8 10 9 5 7

3 1 4 6 8 10 9 5 7 2

发生了第几个历史事件

接下来就用转换后的序列进行计算。为了方便的看出学生答案的顺序是否正确,应该按照正确答案与1 2 … 10的对应关系将学生答案再做一次转换。正确答案中第2个历史件事发生在第1位,那么学生答案中的2应转换为1;即3转换为2,以此类推。学生答案就转换为最终的序列为:

编号

1 2 3 4 5 6 7 8 9 10

序列

2 3 4 5 6 7 8 9 10 1

显而易见,这个序列的最长有序子串(非连序)的长度为9。要将输入的正确答案序列和学生答案序列都依次做上述转换就显得非常麻烦了,其实正确答案序列是无需转换的。假设正确答案序列为A,学生答案序列为B,则发生在第Bi位的历史事件的最终位置就应该是Ai。这样就可以一次完成全部转换。

接下来就是要找出最长有序子串。因为正确的顺序是由小至大,所以从最后一位开始遍例。依次找出每个数之后(含自身)的最长有序子串长度Mi,然后找最Mi中的最大值即为解。观查下面的序列:

编号

1 2 3 4 5 6 7 8 9 10

序列

5 8 3 7 6 1 9 2 10 4

因此在进行传入的参数应该是变换之后的数组。
对于如何将编号转化为序列号,很简单的操作:

for(int i=1; i<=n; i++)    
{
cin>>loc;//事件i发生的位置loc
x[loc]=i;//
}

AC - Code:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<sstream>
#include<vector>
#include<stack>
#include<queue>
#include<cmath>
#include<map>
#include<set>
using namespace std;
typedef long long ll;
typedef pair<int,int> pll;
const int INF = 0x3f3f3f3f;
const int MAXN = 1000 + 5;

int dp[MAXN][MAXN];
int a[MAXN];
int b[MAXN];
int main(){
ios::sync_with_stdio(false);
int n;
int x;
cin >> n;
for(int i = 1; i <= n; i++){
cin >> x;
a[x] = i;
}
while(cin >> x){
memset(dp, 0, sizeof(dp));
b[x] = 1;
for(int i = 2; i <= n; i++){
cin >> x;
b[x] = i;
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
if(a[i] == b[j])
dp[i][j] = dp[i-1][j-1]+1;
else
dp[i][j]=max(dp[i-1][j], dp[i][j-1]);
}
}
cout << dp[n][n] << endl;
}
return 0;
}