小Z的字符串

题目描述

Z Z Z 英语课思考数学题,被英语老师发现啦。

英语老师:「我们定义一个回文串是正方读起来相同的字符串」。
Z Z Z :「这个简单,不就是像 abba aba 这样的吗」。
英语老师:「现在给你一个长度为 n n n 的字符串,要你求出他的最长回文子序列」。
Z Z Z :「子序列是不连续的吧? 好的我知道了」。

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

输入格式

第一行输入 3 3 3 个正整数 n , k , m n, k, m n,k,m k k k 是转换规则的个数。

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

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

输出格式

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

样例

样例输入1:

10 7 6
1 3
5 7
3 5
2 6
2 4
8 4
10 9
1 9 2 3 10 3

样例输出1:

5

数据范围

对于 20 % 20\% 20% 的数据, n , k , m ≤ 10 n, k, m \le 10 n,k,m10
对于 40 % 40\% 40% 的数据, n , k , m ≤ 200 n, k, m \le 200 n,k,m200
对于 100 % 100\% 100% 的数据, n ≤ 1 0 5 , k ≤ 1 0 6 , m ≤ 1 0 3 , 1 ≤ x , y ≤ n n \le 10^5, k \le 10^6, m \le 10^3, 1 \le x, y \le n n105,k106,m103,1x,yn

题解

对于可以转换的数字,把它们用一个数据结构进行维护。

40 % 40\% 40%
最先可以想到用图来存,使用邻接矩阵, f x , y f_{x, y} fx,y 等于 1 1 1,说明 x x x 可以变成 y y y
跑一遍 Floyd,得出能变成的数字。
再跑一遍最长回文子序列即可。

100 % 100\% 100%
使用并查集维护,将可以互相转化的数字合并。
接下来跑一遍最长回文子序列就行。

关于求最长回文子序列:
f i , j f_{i, j} fi,j 表示从 i i i j j j 的最长回文子序列长度。
转移方程

  • 普遍情况下, f i , j = max ⁡ ( f i + 1 , j , f i , j − 1 ) f_{i, j} = \max(f_{i + 1, j}, f_{i, j - 1}) fi,j=max(fi+1,j,fi,j1)
  • 如果 i i i j j j 能转化, f i , j = max ⁡ ( f i , j , f i + 1 , j − 1 + 2 ) f_{i, j} = \max(f_{i, j}, f_{i + 1, j - 1} + 2) fi,j=max(fi,j,fi+1,j1+2)

代码:

int n, k, m;
int u[100010];//并查集
int a[1010];
int f[1010][1010];
//get_father 求 x 属于的集合
int main(){
	scanf("%d %d %d", &n, &k, &m);
	//初始化
	for(int i = 1; i <= n; ++ i){
		u[i] = i;
	}
	for(int i = 1; i <= k; ++ i){
		int x, y;
		scanf("%d %d", &x, &y);
		//合并
		int t1 = get_father(x), t2 = get_father(y);
		if(t1 == t2){
			continue;
		}
		u[t1] = t2;
	}
	输入 a
	//初始化 f
	memset(f, 0, sizeof(f));
	for(int i = 1; i <= m; ++ i){
		f[i][i] = 1;
	}
	//区间 dp
	for(int l = 1; l < m; ++ l){
		for(int i = 1; i <= m - l; ++ i){
			int j = i + l;
			//[i, j]
			f[i][j] = max(f[i + 1][j], f[i][j - 1]);
			if(u[get_father(a[i])] == u[get_father(a[j])]){
				f[i][j] = max(f[i][j], f[i + 1][j - 1] + 2);
			}
		}
	}
	printf("%d", f[1][m]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值