[HDOJ 3525][Vjude 10892] Orienteering [动态规划]

本文介绍了一种解决最长公共子序列问题的高效算法,适用于特定数据规模,通过使用离散集合和二分查找来减少不必要的计算,实现了n*m*log(n)的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

现有两排人,每排有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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值