2021百度之星复赛1002第二类斯特林数

题目

题目链接 Add or Multiply 1

题目大意

有一个初始数 x x x和一个运算序列 a n + m a_{n+m} an+m,其中 a 1 a_1 a1 a n a_n an是加法, a n + 1 a_{n+1} an+1 a n + m a_{n+m} an+m是乘法,令 S = x + a 1 + a 2 + . . . + a n × a n + 1 × a n + 2 × . . . × a n + m S=x+a_1+a_2+...+a_n\times a_{n+1}\times a_{n+2}\times ...\times a_{n+m} S=x+a1+a2+...+an×an+1×an+2×...×an+m

两个运算序列被称为是本质相同的,当且仅当这两个序列的 x x x a i a_i ai取任何值时,得到的 S S S都相同。

给出 n n n m m m,可以重新排列运算序列 a a a,问有多少个本质不同的运算序列。运算序列 a a a从左到右执行。

解题思路

我们将 + a i +a_i +ai记作 0 i 0_i 0i × a i \times a_i ×ai记作 1 i 1_i 1i。最后的运算序列即可表示为一个 01 01 01序列。

设这个 01 01 01序列为
0 1 0 2 0 3 1 4 1 5 1 6 0 7 0 8 0 9 0_10_20_31_41_51_60_70_80_9 010203141516070809

对于任意的 x x x a i a_i ai取值,两个序列得到的值都要相同,那么就要满足对于一个连续相同的子段,包含的元素的个数和值必须相同。对于上述的这个序列,以下的这些 01 01 01序列都和它本质相同:

0 3 0 2 0 1 1 4 1 5 1 6 0 7 0 8 0 9 0_30_20_11_41_51_60_70_80_9 030201141516070809
0 3 0 1 0 2 1 4 1 5 1 6 0 7 0 8 0 9 0_30_10_21_41_51_60_70_80_9 030102141516070809
0 1 0 2 0 3 1 6 1 4 1 5 0 7 0 8 0 9 0_10_20_31_61_41_50_70_80_9 010203161415070809
. . . . . . ... ... ......


情况1: 0 7 0 8 0 9 1 4 1 5 1 6 0 1 0 2 0 3 0_70_80_91_41_51_60_10_20_3 070809141516010203
情况2: 0 1 0 2 0 7 1 4 1 5 1 6 0 1 0 8 0 9 0_10_20_71_41_51_60_10_80_9 010207141516010809
. . . . . . ... ... ......
则本质不同。

让我们先仅考虑其中一种运算,也即 0 0 0序列的排列方案数。

设有分成的 k k k段连续 0 0 0序列,这k段之间可以重排列,这样获得的序列一定本质不同,也即情况 1 1 1,方案数为 k ! k! k! 。每一段序列中的元素不同,序列也一定本质不同,也即情况 2 2 2,要将 n n n不同的数分成 k k k个集合,方案数为第二类斯特林数

将n个相同的物品放进k个相同的集合中,方案数可以用隔板法求。
将n个不相同的物品放进k个相同的集合中,方案数可以用第二类斯特林数求
将n个不相同的物品放进k个不相同的集合中,方案数可以用第二类斯特林数求,只需对集合进行重排列,也即乘上 k ! k! k!

本题就是考察第三个知识点。

代码


#include <bits/stdc++.h>
using namespace std;

const int N = 3e3 + 5;
const long long mod = 1e9 + 7;

long long fact[N];
void init_fact()
{
    fact[0] = 1;
    for (int i = 1; i < N; i++)
        fact[i] = fact[i - 1] * i % mod;
}

long long n, k;
long long S[N][N];
void init_S()
{
    for (int i = 1; i < N; i++)
        S[i][0] = 0, S[i][i] = S[i][1] = 1;
    for (int i = 2; i < N; i++)
        for (int j = 1; j < i; j++)
            S[i][j] = (S[i - 1][j - 1] + 1ll * j * S[i - 1][j] % mod) % mod;
}
void solve()
{
    scanf("%lld %lld", &n, &k);
    if (n < k)
        swap(n, k);
    long long ans = 0;
    for (long long i = 1; i <= k; i++)
    {
        //最左边放一个隔板 中间将n个数分成i个集合的方案数 (最右边同理  方案数*2
        ans = (ans + 2 * S[n][i] % mod * fact[i] % mod * S[k][i] % mod * fact[i] % mod) % mod;

        //两边都放一个数  中间将n个数分成i-1个集合的方案数
        if (i >= 2)
            ans = (ans + S[n][i - 1] * fact[i - 1] % mod * S[k][i] % mod * fact[i] % mod) % mod;

        //两边都不放  中间将n个数分成i+1个集合的方案数
        if (i + 1 <= n)
            ans = (ans + S[n][i + 1] * fact[i + 1] % mod * S[k][i] % mod * fact[i] % mod) % mod;
    }
    printf("%lld\n", ans);
}
int main()
{
    init_fact();
    init_S();
    int t;
    scanf("%d", &t);
    while (t--)
        solve();
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hesorchen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值