jzoj5316 【清华集训2017模拟8.19】merge

本文探讨了两个1到n的排列中不同入栈序列的数量计算问题。通过定义状态转移方程并结合卡特兰数去除重复情况,利用C++实现算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

两个1..n的排列,每次选一个将队首入栈,求有多少种不同的入栈序列。

分析

fi,j 没跑了,考虑如何去重。
转移f[i][j]的时候,假设i,j前面有一段长度为k的相同串。 我们可以发现,将A序列看作左括号,B序列看作右括号,指针从i-k,j-k开始的每一种合法括号序列都可以被对称操作从而获得一种重复。 (这能覆盖所有重复的情况)
因此我们枚举上一次指针的位置i-k,j-k,然后减去重复数。 (也就是合法括号序列的对数)
但是注意到类似 (()) () 这样的括号序列,已经被k’< k去过重了,因此要预先给左右加上一对大括号将整个括起来。所以括号对数减去一对。
合法括号序列的个数就是卡特兰数

demo

#include <cstdio>
#include <iostream>
using namespace std;
typedef long long ll;
const ll N=2e3+10,mo=1e9+7;
int n,ans;
int a[N],b[N];
int sj[N*2][N];
int f[N][N],g[N][N],c[N];
inline void add(int &x,int y) {x=(ll)(x+y)%mo;}
ll ksm(ll x,ll y) {
    if (y==1) return x;
    ll z=ksm(x,y>>1);
    return z*z%mo*((y&1)?x:1)%mo;
}
void init() {
    sj[0][0]=1;
    for (int i=1; i<=2*n; i++) for (int j=0; j<=n; j++) {
        sj[i][j] = sj[i-1][j];
        if (j) add(sj[i][j], sj[i-1][j-1]);
    }
    for (int i=0; i<=n; i++) 
        c[i] = (ll) ksm(i+1,mo-2) * sj[2*i][i] % mo;
    for (int i=1; i<=n; i++) for (int j=1; j<=n; j++) if (a[i]==b[j]) {
        g[i][j]=g[i-1][j-1]+1;
    } else g[i][j]=0;
}
int main() {
    freopen("merge.in","r",stdin);
    freopen("merge.out","w",stdout);
    cin>>n;
    for (int i=1; i<=n; i++) scanf("%d",&a[i]);
    for (int i=1; i<=n; i++) scanf("%d",&b[i]);
    init();
    f[0][0]=1;
    for (int i=0; i<=n; i++) for (int j=0; j<=n; j++) {
        if (i+j==0) continue;
        f[i][j] = (f[i-1][j] + f[i][j-1]) % mo;
        if (g[i][j]) {
            int len=g[i][j];
            for (int k=1; k<=len; k++)
                add(f[i][j], (ll) - f[i-k][j-k] * c[k-1] % mo);
        }
    }
    printf("%d\n",(f[n][n]+mo)%mo);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值