题意简述:给定两个长度为 \(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;
}
标签:排列,int,题解,序列,1005,山札,转移,mod
From: https://www.cnblogs.com/Laoli-2020/p/16717848.html