NKOJ 用魔法生成回文序列

用魔法生成回文序列

Time Limit : 1000MS Memory Limit : 65536KB

Description

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

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

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

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

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

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

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

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

Input Format

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

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

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

Output Format

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

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

Sample Output
5

题目大意:给你一个数字串,要你求出其中最长的回文子序列,其中一些数字可以互换 很明显回文子序列的左右两端必须相等,因此我们只需要判断两个数字是否能通过变化变成一样的数字即可。

Part 1

能否通过变化变成一样的数字?所以你想到了什么?反正本蒟蒻想到了并查集。

做法:每读入两个数a,b,则a可以变成b,b也可以变成a,于是我们让a所在集合的根节点的父亲等于b所在集合的根节点。

在判断的时候,只需要检查他们的根节点是否相同即可。

排除了数字互相变化的干扰,终于可以好好做题了。

Part 2

在一个序列中找到最长回文子序列,可不可以按照LIS来做呢?回文序列并不是要求相邻两个数字满足一定条件,显然不行。

那么重新设计动规方案:

状态:

f i , j f_{i,j} 表示在区间 i , j i, j 内的最长回文子序列长度。

状态转移方程:

f i , j = m a x { f i + 1 , j 1 + 2 f i , j 1 f i + 1 , j f_{i,j}=max\begin{cases}f_{i+1,j-1}+2(当序列首尾数字可以变为相同数字时) \\ f_{i,j - 1}\\f_{i+1,j} \\ \end{cases}

阶段:

有了刚才的方程,显然每个状态依赖于起点在他之后的状态和终点在他之前的状态,那么只需要枚举从 m m 枚举到 1 1 作为起点i,终点 j j 再从 i + 2 i+2 枚举到 n n 即可。显然 :
f i , i = 1 f_{i,i}=1

f i , i + 1 f_{i,i+1} 当首尾数字可以变为相同数字时为2,否则为1.

好了分析就到这里了。

Code:

#include <cstdio>

int father[1000005], f[1005][1005], A[1005];

int getfather(int x)
{
	return father[x] == x ? x : father[x] = getfather(father[x]);
}//并查集

int main()
{
	int m, n, k, a, b, ans = 1;
	scanf("%d%d%d", &m, &k, &n);
	for (int i = 1; i <= m; i ++)
	father[i] = i;//千万不要忘了这个初始化
	for (int i = 1; i <= k; i ++)
	{
		scanf("%d%d", &a, &b);
		father[getfather(a)] = getfather(b);//数字a可以变为数字b
	}
	for (int i = 1; i <= n; i ++)
	scanf("%d", A + i);
	for (int i = 1; i <= n; i ++)
	{
		f[i][i] = 1;
		if (getfather(A[i]) == getfather(A[i + 1]))
		f[i][i + 1] = 2;
		else f[i][i + 1] = 1;
	}//预处理一下,开始动规
	for (int i = n; i >= 1; i --)//阶段划分的顺序不要搞错
	for (int j = i + 2; j <= n; j ++)
	if (getfather(A[i]) == getfather(A[j]))//以下就是状态转移方程
	f[i][j] = f[i + 1][j - 1] + 2;
	else if (f[i + 1][j] >= f[i][j - 1])
	f[i][j] = f[i + 1][j];
	else f[i][j] = f[i][j - 1];
	for (int i = 1; i <= n; i ++)
	for (int j = i + 1; j <= n; j ++)
	if (f[i][j] > ans) ans = f[i][j];
	printf("%d", ans);
	return 0;//程序中最漂亮的一句话
}

猜你喜欢

转载自blog.csdn.net/jvruo_shabi/article/details/108187443