Codechef CARDLINE
题目描述
从前有$N $张卡片,在桌上摊成了一排。每张卡片上有两个数字,一个写在上边,一个写在下
边,每个数字都是 $1 $到 \(N\)之间的一个整数(也包含\(1\)和$ N$)。同时,在所有卡片的上边的数字
中,\(1\)到 \(N\)的每个数字恰好出现了一次。下边的数字也一样。
大厨想要给这些卡片重新排个序。他希望在重排之后,卡片上边的数字构成的序列,还有卡
片下边的数字构成的序列,这两个序列的最长公共子串尽量长。这里子串的意思是一段连续的子
序列。但他不能涂改卡片上的数字,也没法把卡牌倒过来放,也就是说,原本在上边的数字还在
上边,下边亦同。
请你求出最长公共子串的最大长度
数据范围
• \(1 ≤ T ≤ 100\)
• \(1 ≤ B ≤ 2000\)
• \(1 ≤ Ai,Bi ≤ N\)
• A 序列和 B 序列中的元素两两不同。
解题思路
将每个卡片看成点,对于所有的\((i,j)\) \((a_i==b_j)\)连边
可以发现图一定是若干个环组成
继续观察可以发现将环上的一条链拿出来对答案的贡献显然是链的长度-1,大致的构造如下:
1 2 3 4 5
2 3 4 5 1
对于上面的这种构造显然可以再插入长度相等或长度+1的链,其他则不能保证连续相等
那么我们就枚举\(k\)表示最后只留下\(k\)和\(k+1\)的链,然后加其他链拆成这两种长度
我们只需要提前构造一个\(f[i][j]\)表示将\(j\)的链拆成长度\(i\)和\(i+1\)的最大贡献
考虑累计答案的时候,我们需要记录\(sum[i]\)表示长度为\(i\)的链有多少,只枚举\(sum[i]>0\)的长度即可
效率\(O(T*n*\sqrt{n})\)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=2005;
int f[maxn][maxn],T,n,a[maxn],b[maxn],h[maxn],sum[maxn],ans,que[maxn],til;
bool vis[maxn];
void work(){
memset(vis,0,sizeof(vis));
memset(sum,0,sizeof(sum));
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<=n;i++) scanf("%d",&b[i]),h[b[i]]=i;
for (int i=1;i<=n;i++) if (!vis[i]){
int s=0,x=i;while(!vis[x]) s++,vis[x]=1,x=h[a[x]];sum[s]++;
}
ans=sum[1];til=1;
for (int i=1;i<=n;i++) if (sum[i]) que[++til]=i;
for (int k=1;k<=n;k++){
int num=0;
for (int i=1;i<=til;i++) num+=sum[que[i]]*f[k][que[i]];
ans=max(num,ans);
}
printf("%d\n",ans);
}
int main(){
freopen("exam.in","r",stdin);
freopen("exam.out","w",stdout);
for (int i=1;i<=2000;i++)
for (int j=1;j<=2000;j++){
if (j-i>=0) f[i][j]=max(f[i][j],f[i][j-i]+i-1);
if (j-i-1>=0) f[i][j]=max(f[i][j],f[i][j-i-1]+i);
}
scanf("%d",&T);while(T--) work();
return 0;
}