Exam Records 6

10.31

多校 NOIP2024 模拟赛 16

D

逆序图

题目描述

“真开心呢,凤同学。”——Asahina Mafuyu。
给出一个长度为 n 的排列 P 和一个定义在集合 {1,2,3,,n1} 上的函数 f ,我们称该排列“生成”的图为这样的一张图 G

  1. G 具有编号为 1nn 个顶点。
  2. 如果 i<jPi>Pj (即 (i,j) 为一个逆序对),那么在 G 中存在一条无向边 (i,j)

在此基础上,如果 D 是长度为正偶数的顶点序列 {D1,D2,D2m1,D2m},其中 m 是任意的正整数,并且满足以下性质:

  1. 1k<2mDk<Dk+1
  2. 1km,存在一条无向边 (D2k1,D2k)
  3. 1j,kmjk,图中不存在一条起点是 D2j,终点是 D2k 的简单路径。
    (即选择若干条顶点编号递增的边,并且任意两条边不在同一个连通块中)

则我们称序列 D 是“开心”的,记该序列的权值为 k=1mf(PD2k1PD2k)

定义一个排列的权值为所有“开心”的序列 D 的权值之和,即

D is happyk=1|D|2f(PD2k1PD2k)

现在,你需要求出所有排列的权值之和对 998244353 取模的结果。

1n50001f(i)109

排列图的连通块是一个区间。

对于每一个连通块求出答案。设 gi 表示假设 1i 中的所有排列都是一个连通块的权值和。fi 表示 1i 的排列是一个连通块的权值和。

然后对于每个连通块,可以跳过,可以经过,可以从它开始。

代码
#include <cstdio>
#include <algorithm>

#define int long long

using namespace std;

const int N = 5005;
const int mod = 998244353;
const int inv = mod + 1 >> 1;

int n, a[N], F[N], fac[N], f[N][2], g[N][2];

// f[i][0], g[i][0] 表示方案数,f[i][1], g[i][1] 表示权值和。

signed main()
{
    freopen("graph.in", "r", stdin);
    freopen("graph.out", "w", stdout);

    scanf("%lld", &n);
    for(int i = 1; i < n; ++ i)
        scanf("%lld", &a[i]);

    fac[0] = 1;
    for(int i = 1; i <= n; ++ i)
        fac[i] = fac[i - 1] * i % mod;

    g[0][0] = 1;
    for(int i = 1; i <= n; ++ i)
        g[i][0] = g[i - 1][0] * i % mod;
    for(int i = 2; i <= n; ++ i)
        for(int j = 1; j < i; ++ j)
            g[i][1] = (g[i][1] + a[j] * (i - j) % mod * fac[i - 2] % mod * i % mod * (i - 1) % mod * inv) % mod;
    
    f[1][0] = 1;
    for(int i = 2; i <= n; ++ i)
    {
        f[i][0] = g[i][0], f[i][1] = g[i][1];
        for(int j = 1; j < i; ++ j)
        {
            f[i][0] = (f[i][0] - g[j][0] * f[i - j][0]) % mod;
            f[i][1] = (f[i][1] - g[j][0] * f[i - j][1] - g[j][1] * f[i - j][0]) % mod;
        }
    }

    for(int i = 1; i <= n; ++ i)
        for(int j = 0; j < i; ++ j)
            F[i] = (F[i] + F[j] * f[i - j][1] + F[j] * f[i - j][0] + g[j][0] * f[i - j][1]) % mod;

    printf("%lld\n", (F[n] + mod) % mod);

    return 0;
}

11.1

多校 NOIP2024 模拟赛 17

C

集合

题目描述

彩梦喜欢一个集合 S,当且仅当存在另一个集合 T ,使得 (S,T) 满足以下条件:

  • |S|=n,|T|=m,1S
  • S[1,nm]Z,TZ
  • {i+jiS,jT}={1,2,,nm}

给定 n,m,求彩梦喜欢的集合数量对质数 998244353 取模的值。
为了方便选手,nm 都使用唯一分解形式给出。

给定的 n,m 分别为 pixipiyi

0xi,yi2×105

如何判断一个集合 S 是否合法。

根据 S 构造一个长为 nm01 字符串,字符串第 i 位为 1 当且仅当 iS。可以重复以下两种操作。

  • 如果对于 k 满足 [ki,k(i+1)1] 全部相同,那么将 [ki,k(i+1)1] 替换为一个字符。

  • 找到间隔最小的一对 1,设间隔为 k,将所有长度为 k100 替换为 1,所有长度为 k00 替换为 0

如果字符串最终能变成 1 则合法。

记二元组 (x,y) 表示字符串中 1 的数量和串长。相当于从 (1,1) 开始轮流进行以下操作,答案为最终变成 (n,nm) 的方案数。

  • (x,y)(kx,ky)

  • (x,y)(x,ky)

一操作和二操作都可以当做操作序列的开头,也都可以当做操作序列的结尾。

因为最终要变成 (n,nm),所以一操作 k=n,二操作 k=m。发现两种操作独立。对于每一种操作设 fi 表示进行 i 次操作的方案数。

比如第一种操作,相当于有一些种球,第 x 种球有 ax 个,要放到 i 个不同盒子,不可空的方案数。

二项式反演,设 gi 为可空的方案数。由于 ai2×105,所以不同的 ai 只有根号种,gi 可以直接计算。求 fi 套 FFT 即可。

代码
#include <cstdio>
#include <cassert>
#include <algorithm>

#define int long long

using namespace std;

const int N = 600005;
const int mod = 998244353;

int n = 600000, m, ans, lim, l, I, rev[N], a[N], b[N], f[N], g[N], h[N], fac[N], ifa[N];

int Pow(int x, int y)
{
    int r = 1;
    for(; y; y >>= 1, x = x * x % mod)
        if(y & 1)
            r = r * x % mod;
    return r;
}

void init(int n)
{
    for(l = 0, lim = 1; lim < n; lim <<= 1, ++ l);
    I = Pow(lim, mod - 2);
    for(int i = 0; i < lim; ++ i)
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << l - 1);
}

void NTT(int *a, int type)
{
    for(int i = 0; i < lim; ++ i)
        if(i < rev[i])
            swap(a[i], a[rev[i]]);
    for(int i = 1; i < lim; i <<= 1)
    {
        int x = Pow(type ? 3 : (mod + 1) / 3, (mod - 1) / (i << 1));
        for(int j = 0; j < lim; j += (i << 1))
        {
            for(int k = 0, y = 1; k < i; ++ k, y = y * x % mod)
            {
                int p = a[j + k], q = y * a[i + j + k] % mod;
                a[j + k] = (p + q) % mod;
                a[i + j + k] = (p - q) % mod;
            }
        }
    }
    if(!type)
        for(int i = 0; i < lim; ++ i)
            a[i] = a[i] * I % mod;
}

int C(int n, int m)
{
    return fac[n] * ifa[m] % mod * ifa[n - m] % mod;
}

signed main()
{
    freopen("set.in", "r", stdin);
    freopen("set.out", "w", stdout);

    fac[0] = ifa[0] = 1;
    for(int i = 1; i <= n; ++ i)
        fac[i] = fac[i - 1] * i % mod;
    ifa[n] = Pow(fac[n], mod - 2);
    for(int i = n - 1; i >= 0; -- i)
        ifa[i] = ifa[i + 1] * (i + 1) % mod;

    scanf("%lld", &m);
    for(int i = 1, x, u, v; i <= m; ++ i)
        scanf("%lld%lld%lld", &x, &u, &v), ++ a[u], ++ b[v];

    for(int i = 1; i <= 2e5; ++ i)
        f[i] = g[i] = 1;
    for(int i = 1; i <= 2e5; ++ i)
        if(a[i])
            for(int j = 1; j <= 2e5; ++ j)
                f[j] = f[j] * Pow(C(i + j - 1, j - 1), a[i]) % mod;
    for(int i = 1; i <= 2e5; ++ i)
        if(b[i])
            for(int j = 1; j <= 2e5; ++ j)
                g[j] = g[j] * Pow(C(i + j - 1, j - 1), b[i]) % mod;

    for(int i = 0; i <= 2e5; ++ i)
    {
        f[i] = f[i] * ifa[i] % mod;
        g[i] = g[i] * ifa[i] % mod;
        h[i] = ifa[i] * (i & 1 ? -1 : 1) % mod;
    }

    init(4e5 + 1);
    NTT(f, 1);
    NTT(g, 1);
    NTT(h, 1);
    
    for(int i = 0; i < lim; ++ i)
    {
        f[i] = f[i] * h[i] % mod;
        g[i] = g[i] * h[i] % mod;
    }

    NTT(f, 0);
    NTT(g, 0);

    for(int i = 0; i < lim; ++ i)
    {
        f[i] = f[i] * fac[i] % mod;
        g[i] = g[i] * fac[i] % mod;
    }

    for(int i = 1; i <= 2e5; ++ i)
    {
        ans = (ans + 2 * f[i] * g[i]) % mod;
        if(i)
            ans = (ans + f[i] * g[i - 1]) % mod;
        if(i != 2e5)
            ans = (ans + f[i] * g[i + 1]) % mod;
    }

    printf("%lld\n", (ans + mod) % mod);

    return 0;
}
posted @   Estelle_N  阅读(51)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· 本地部署DeepSeek后,没有好看的交互界面怎么行!
· 趁着过年的时候手搓了一个低代码框架
· 推荐一个DeepSeek 大模型的免费 API 项目!兼容OpenAI接口!
点击右上角即可分享
微信分享提示