现有两排人,每排有n组人,每组人有m个,每个人到这排时,都先查看自己最喜欢的位置,如果他最喜欢的位置上有人了,他就去那个位置的后一个位置,如果那个位置还有人,就再去下一个,以此类推。
在每个人都找到了自己的位置之后,我们从左到右把每个人的组号编成一个序列,即一个n*m长度,每个元素都是1~n,每个数字出现了m次的序列。
现在求这两排人所形成的两个序列的最长公共子序列。
数据范围:n不超过10^4,m不超过10
定义状态dp[i]表示,在当前条件j下,第一个序列的整体和第二个序列的长度为j的前缀所能形成的长度为i的公共子序列的右侧的最左值为多少。
因为每个数出现的次数不超过m,所以每当j增加1的时候,最多仅有m个位置能够形成新的最长公共子序列,即最多更新m个位置的值,查找这m个位置使用二分查找
复杂度n*m*log(n)
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
inline int in() {
char c=getchar();
while (c<'0'||c>'9') c=getchar();
int ans=0;
while (c>='0'&&c<='9') {
ans=ans*10+c-'0';
c=getchar();
}
return ans;
}
struct DisJoinSet {
int a[200100];
void clear(int n) {
for (int i=0;i<=n;i++) a[i]=i;
}
int get(int i) {
if (a[i]==i) return i;
return a[i]=get(a[i]);
}
void tosame(int i,int j) {
i=get(i);
j=get(j);
a[i]=j;
}
};
int n,m,ans,nn;
int s1[200010];
int s2[200010];
DisJoinSet next1,next2;
void read(int s[],DisJoinSet &next) {
int x,y,i,j;
memset(s,0,sizeof(s1));
next.clear(nn+nn);
for (i=0;i<nn;i++) {
x=in();y=in();
y=next.get(y);
s[y]=x;
next.tosame(y,y+1);
}
for (i=0,j=0;i<nn;i++,j++) {
while (s[j]==0) j++;
s[i]=s[j];
}
}
void print(int s[]) {
for (int i=0;i<nn;i++)
printf("%d ",s[i]);
printf("\n");
}
int pos[10010][10];
int num[10011];
int dp[100010];
int maxl;
int main() {
int t,tt,i,j,k;
scanf("%d",&t);
for (tt=1;tt<=t;tt++) {
scanf("%d%d",&n,&m);
nn=n*m;
read(s1,next1);
read(s2,next2);
memset(num,0,sizeof(num));
for (i=0;i<nn;i++) {
pos[s1[i]][num[s1[i]]++]=i;
}
maxl=0;
dp[0]=-1;
//print(s1);
//print(s2);
for (j=0;j<nn;j++) {
//printf("--%d %d\npos: ",j,s2[j]);
for (k=m-1;k>=0;k--) {
//printf("%d ",pos[s2[j]][k]);
int *t=lower_bound(dp,dp+maxl+1,pos[s2[j]][k]);
if (t==dp+maxl+1) maxl++;
*t=pos[s2[j]][k];
}
//printf("\n");
//for (k=0;k<=maxl;k++) printf("%d ",dp[k]);
//printf("\n");
}
printf("Case #%d: %d\n",tt,maxl);
}
return 0;
}