题目描述
小 Z 上英语课思考数学问题被英语老师发现啦~
英语老师:「你这么爱胡思乱想我问你一道英语题吧」
小 Z 想跑,但是已经来不及了。
英语老师:「我们定义一个回文串是正方读起来相同的字符串」
小 Z:「这个简单,不就是像 "abba""abba""abba""aba""aba""aba"这样的吗」
英语老师:「现在给你一个长度为 nnn 的字符串,要你求出他的最长回文子序列」
小 Z:「子序列是不连续的吧? 好的我知道了」
小 Z 轻松的解决了这个问题,并把他修改了一下交给你。
现在一个字符串变成了 mmm 个数字,会魔法的小 Z 可以把一个数字 xxx 根据变换规则变成 yyy,给定所有的变换规则,要你求出这个数字串的最长回文子序列。
输入格式
第一行输入 3 个正整数 nnn,kkk 和 mmm,kkk 是转换规则的个数。
第二行开始的 k 行,每行两个正整数 xxx 和 yyy,表示数字 xxx 可以变成数字 yyy, 并且数字 yyy 可以变成数字 xxx。注意,如果数字 xxx 可以变成数字 yyy,并且数字 yyy 可以变成数字 zzz,那么数字 xxx 也可以便成数字 zzz。xxx 可能等于 yyy,同一对(x,y)(x, y)(x,y)可能重复出现。
最后一行输入 mmm 个正整数,表示题目中所提的数字串,每个数 ≤n\le n≤n。
输出格式
输出一行一个数表示答案。
数据范围
对于 20% 的数据:n,k,m≤10n,k,m\le10n,k,m≤10;
对于 40% 的数据:n,k,m≤200n,k,m\le200n,k,m≤200;
对于 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 nn≤105,k≤106,m≤103,1≤x,y≤n。
分析
首先可以想到的是,对于可以互相转化的数字,把他们都变成一样的数字会有最优解。于是可以用并查集维护,将可以互相转化的数字合并,最后将每个数变成它的祖先。然后可以用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][j−1],f[i+1][j−1]+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;
}