「NOIP2018模拟赛」 T4 - 并查集+区间Dp

探讨了在数字串及变换规则下求解最长回文子序列的问题,通过并查集与DP算法优化解决方案。

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

题目描述

小 Z 上英语课思考数学问题被英语老师发现啦~

英语老师:「你这么爱胡思乱想我问你一道英语题吧」

小 Z 想跑,但是已经来不及了。

英语老师:「我们定义一个回文串是正方读起来相同的字符串」

小 Z:「这个简单,不就是像 "abba""abba""abba""aba""aba""aba"这样的吗」

英语老师:「现在给你一个长度为 nnn 的字符串,要你求出他的最长回文子序列」

小 Z:「子序列是不连续的吧? 好的我知道了」

小 Z 轻松的解决了这个问题,并把他修改了一下交给你。
  
现在一个字符串变成了 mmm 个数字,会魔法的小 Z 可以把一个数字 xxx 根据变换规则变成 yyy,给定所有的变换规则,要你求出这个数字串的最长回文子序列。

输入格式

第一行输入 3 个正整数 nnnkkkmmmkkk 是转换规则的个数。

第二行开始的 k 行,每行两个正整数 xxxyyy,表示数字 xxx 可以变成数字 yyy, 并且数字 yyy 可以变成数字 xxx。注意,如果数字 xxx 可以变成数字 yyy,并且数字 yyy 可以变成数字 zzz,那么数字 xxx 也可以便成数字 zzzxxx 可能等于 yyy,同一对(x,y)(x, y)(x,y)可能重复出现。

最后一行输入 mmm 个正整数,表示题目中所提的数字串,每个数 ≤n\le nn

输出格式

输出一行一个数表示答案。

数据范围

对于 20% 的数据:n,k,m≤10n,k,m\le10n,k,m10

对于 40% 的数据:n,k,m≤200n,k,m\le200n,k,m200

对于 100% 的数据:n≤105,k≤106,m≤103,1≤x,y≤nn \le 10^5,k \le 10^6,m \le 10^3,1 \le x,y \le nn105k106m1031xyn

分析

首先可以想到的是,对于可以互相转化的数字,把他们都变成一样的数字会有最优解。于是可以用并查集维护,将可以互相转化的数字合并,最后将每个数变成它的祖先。然后可以用Dp。定义f[i][j]f[i][j]f[i][j]为区间[i,j][i,j][i,j]内的最长回文子序列,容易知道f[i][i]=1f[i][i]=1f[i][i]=1。然后就可以知道:
f[i][j]=max⁡{f[i+1][j],f[i][j−1],f[i+1][j−1]+2(a[i]==b[j])}f[i][j]=\max\{f[i+1][j],f[i][j-1],f[i+1][j-1]+2(a[i]==b[j])\}f[i][j]=max{f[i+1][j],f[i][j1],f[i+1][j1]+2(a[i]==b[j])}
然后就可以愉快的ACACAC

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <ctime>
using namespace std;
typedef long long LL;
const int N=1e5+5;
int f[N];
int d[1005][1005];
int n,k,m;
int a[1005],ans;
int getf(int x) {
	return f[x]==x?x:f[x]=getf(f[x]);
}
int main() {
//	freopen("d.in","r",stdin);
//	freopen("d.out","w",stdout);
	scanf("%d%d%d",&n,&k,&m);
	for (int i=1;i<=n;i++) {
		f[i]=i;
	}
	for (int i=1;i<=k;i++) {
		int u,v;
		scanf("%d%d",&u,&v);
		u=getf(u);
		v=getf(v);
		if (u!=v) f[v]=u;
	}
	for (int i=1;i<=m;i++) {
		scanf("%d",&a[i]);
		a[i]=getf(a[i]);
		d[i][i]=1;
	}
	for (int len=2;len<=m;len++) {
		for (int i=1;i+len-1<=m;i++) {
			int j=i+len-1;
			if (a[i]==a[j]) d[i][j]=d[i+1][j-1]+2;
			else d[i][j]=max(d[i][j-1],d[i+1][j]);
		}
	}
	printf("%d",d[1][m]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值