Title

2 つの山札题解

题目链接

题意简述:给定两个长度为 \(n\) 的排列 \(A\)\(B\),按照一下方式生成一个长度为 \(2n-2\) 的序列:你对每一个排列分别做 \(n-1\) 次操作,每一次选择一个序列进行操作,删除该排列的第一个数,将另一个排列第一个数加入序列,问最终能得到多少种序列。

数据范围:\(1 \le n \le 10^3\)

有一个很显然的dp,设 \(f_{i,j}\) 表示保留第一个排列中第 \(i\)\(n\) 个元素和第二个排列中第 \(j\)\(n\) 个元素,进行操作直到两个序列的长度都为 \(1\),能得到的序列数目。

这个dp有一个很naive的转移:\(f_{i,j}=f_{i+1,j}+f_{i,j+1}\),即要么操作序列一,要么操作序列二,倒序枚举计算 \(f\) 数组即可。

但是这样转移会算重,我们来考虑如何去重。

首先,只有 \(A_i=B_j\) 的时候才会算重,而且 \(f_{i.j}\) 多算的部分一定是 \(f_{i+1,j}\)\(f_{i,j+1}\) 中相同的序列数。

\(g_{i,j,k}\) 表示 \(f_{i+k,j}\)\(f_{i,j+k}\) 中相同的序列数,状态数 \(n^3\) 级别,显然不行。

但是容易发现,只有当 \(A_i=B_j\) 时,\(g_{i,j,k}\) 才有意义,又由于序列 \(A\)\(B\) 都是排列,一个 \(i\) 唯一对应一个 \(j\),因此 \(i\)\(j\) 中只有一个需要表示进状态,即记 \(g_{i,k}\) 即可。

考虑转移,首先,\(g_{i,k}\)\(f_{i+k,j}\)\(f_{i,j+k}\) 中的重复序列数,其中可能处于此时排列中的第一个位置的数只有可能是 \(a_i\)\(b_j\)\(a_{i+k}\)\(b_{j+k}\),其中可能相等的数对只有 \((a_i,b_j)\)\((a_{i+k},b_{j+k})\)

因此有两种转移,一种是 \(f_{i+k,j}\)\(f_{i,j+k}\) 分别从 \(f_{i+k+1,j}\)\(f_{i,j+k+1}\) 转移过来,即 \(g_{i,k} \leftarrow g_{i,k+1}\) ,条件是 \(a_i=b_j\) ,根据 \(g\) 的定义,一定满足。

另一种是 \(f_{i+k,j}\)\(f_{i,j+k}\) 分别从 \(f_{i+k,j+1}\)\(f_{i+1,j+k}\) 转移过来,条件是 \(a_{i+k}=b_{j+k}\)

但是注意,第二种转移不能直接认为是 \(g_{i,k} \leftarrow g_{i+1,k-1}\),因为 \(a_{i+1}\)\(b_{j+1}\) 不一定相等,该转移的正确方式应该为 \(g_{i,k} \leftarrow g_{i+k,1-k}\)

由于上述转移中用到了 \(g_{i,k}(k \le 0)\),因此 \(k\) 为非正数的 \(g\) 也需要计算,其中 \(g_{i,0}=f_{i,j}\)\(k\) 为负数的转移和正数一致,但是要注意,\(g_{i,k}(k <0)\) 的计算不能在倒序枚举到 \(i\) 时计算,而应该在枚举到 \(i+k\) 时计算,因为枚举到 \(i\) 时,\(f_{i-k,j}\) 还没有被计算,因此此时的 \(g_{i,k}\) 还没有意义。

然后 \(f\) 的转移就很简单了,\(f_{i,j}=f_{i+1,j}+f_{i,j+1}-[a_i==b_j]g_{i,1}\),初值为 \(f_{n,n}=1\)

倒序枚举一下转移 \(f\)\(g\) 即可,注意 \(g_{i,k}(k > 0)\)要在 \(f_{i,j}\) 之前计算,因为 \(f\) 的转移会用到,\(g_{i,0}\) 要在 \(f_{i,j}\) 之后计算,因为它需要从 \(f_{i,j}\) 转移过来。

Code:

#include<bits/stdc++.h>
using namespace std;
const int mod=1000000007;
int n,a[1005],b[1005];
int f[1005][1005];
map<int,int> g[1005];
int id1[1005],id2[1005];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]),id1[a[i]]=i;
	for(int i=1;i<=n;i++)
		scanf("%d",&b[i]),id2[b[i]]=i;//记双射,方便查询与 a[i] 相同的 b[j]
	for(int i=n;i>=1;i--)
	{
		int k=id2[a[i]];
		for(int j=n-max(i,k);j>=1;j--)//计算g[i][j](j>0)
		{
			g[i][j]=g[i][j+1];
			if(a[i+j]==b[k+j])
				g[i][j]=(g[i][j]+g[i+j][1-j])%mod;
		}
		for(int j=-1;j>=-n;j--)//计算g[i-j][j](j<0)
		{
			if(i-j>n)
				break;
			int k=id2[a[i-j]];
			g[i-j][j]=g[i-j][j+1];
			if(a[i]==b[k+j])
				g[i-j][j]=(g[i-j][j]+g[i][1-j])%mod;
		}
		for(int j=n;j>=1;j--)//计算f[i][j]
		{
			if(i+j==2*n)
				f[i][j]=1;
			else
			{
				f[i][j]=(f[i+1][j]+f[i][j+1])%mod;
				if(a[i]==b[j])
					f[i][j]=(f[i][j]-g[i][1]+mod)%mod;
		}
		g[i][0]=f[i][k];//计算g[i][0]
	}
	printf("%d\n",f[1][1]);
	
	return 0;
}
posted @ 2022-09-22 08:06  五百年前  阅读(24)  评论(1编辑  收藏  举报