2 つの山札题解
题意简述:给定两个长度为 的排列 和 ,按照一下方式生成一个长度为 的序列:你对每一个排列分别做 次操作,每一次选择一个序列进行操作,删除该排列的第一个数,将另一个排列第一个数加入序列,问最终能得到多少种序列。
数据范围:。
有一个很显然的dp,设 表示保留第一个排列中第 到 个元素和第二个排列中第 到 个元素,进行操作直到两个序列的长度都为 ,能得到的序列数目。
这个dp有一个很naive的转移:,即要么操作序列一,要么操作序列二,倒序枚举计算 数组即可。
但是这样转移会算重,我们来考虑如何去重。
首先,只有 的时候才会算重,而且 多算的部分一定是 和 中相同的序列数。
设 表示 与 中相同的序列数,状态数 级别,显然不行。
但是容易发现,只有当 时, 才有意义,又由于序列 和 都是排列,一个 唯一对应一个 ,因此 和 中只有一个需要表示进状态,即记 即可。
考虑转移,首先, 是 和 中的重复序列数,其中可能处于此时排列中的第一个位置的数只有可能是 、、 和 ,其中可能相等的数对只有 和 。
因此有两种转移,一种是 和 分别从 和 转移过来,即 ,条件是 ,根据 的定义,一定满足。
另一种是 和 分别从 和 转移过来,条件是 。
但是注意,第二种转移不能直接认为是 ,因为 和 不一定相等,该转移的正确方式应该为 。
由于上述转移中用到了 ,因此 为非正数的 也需要计算,其中 , 为负数的转移和正数一致,但是要注意, 的计算不能在倒序枚举到 时计算,而应该在枚举到 时计算,因为枚举到 时, 还没有被计算,因此此时的 还没有意义。
然后 的转移就很简单了,,初值为 。
倒序枚举一下转移 和 即可,注意 要在 之前计算,因为 的转移会用到, 要在 之后计算,因为它需要从 转移过来。
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现