bzoj 5306 [HAOI2018] 染色

bzoj 5306 [HAOI2018] 染色

链接

推式子题

首先枚举有几种颜色选择恰好 \(s\) 次,可以得到一个式子:

\[\sum _{i = 0} ^ {\min(\frac n s,m )} \frac {\binom n {i \cdot s} \binom m i (i \cdot s) ! \cdot f_i \cdot (m-i)^{n - is}} {(s!) ^ i} \]

但是,\(f_i\) 不能单纯地等于 \(w_i\), 因为会重复计算,我们不能保证当前选择的 \(i\) 种颜色之外的颜色是否没有出现恰好 \(s\) 次的

发现对于一种染色方案而言,假设其中选择恰好 \(s\) 次的颜色数量为 \(i\),那么他在枚举到 \(j\) 种颜色出现 \(s\) 次是被算进去 \(\binom i j\) 次,所有被算进去的 \(f\) 值之和就是 \(\sum _{j = 0} ^ i (f_j \cdot \binom i j)\)

我们令 \(w_i = \sum _{j = 0} ^i (f_j \cdot \binom i j)\), 那么这样可以推出来 \(f\) 序列,并且把这里的 \(f\) 序列代入之前的式子就可以求出答案了

怎么求 \(f\) 呢?有两种方式

  1. 发现 \(\sum _{j = 0} ^ i \frac{f_j} {j !} \cdot \frac 1 {(i - j) !} = \frac {w_i} {i!}\),可以用多项式除法直接求得 \(\frac {f_j} {j!}\)
  2. 倒推。

    $w_0 = f_0 $

    \(w_1 = f_0 + f_1\)

    \(w_2=f_0+2f_1+f_2\)

    \(w_3=f_0+3f_1+3f_2+f_3\)

    \(\dots\)

    解得

    \(f_0=w_0\)

    \(f_1=w_1-w_0\)

    \(f_2=w_2-2w_1+w_0\)

    \(f_3=w_3-3w_2+3w_1-w_0\)

    找规律,就是 \(f_i = \sum _{j = 0} ^ i (-1)^{i-j} \cdot \binom i j \cdot w_j = i! \cdot \sum_{j=0}^i \frac {(-1)^{i-j}} {(i-j)!} \cdot \frac {w_j} {j!}\)

    卷积即可求出 \(f_i\)

惊讶地发现,\(1004535809\) 这个模数的原根也是 \(3\)

代码

// Copyright lzt
#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<iostream>
#include<queue>
#include<string>
#include<ctime>
using namespace std;
typedef long long ll;
typedef std::pair<int, int> pii;
typedef long double ld;
typedef unsigned long long ull;
typedef std::pair<long long, long long> pll;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rep(i, j, k)  for (register int i = (int)(j); i <= (int)(k); i++)
#define rrep(i, j, k) for (register int i = (int)(j); i >= (int)(k); i--)
#define Debug(...) fprintf(stderr, __VA_ARGS__)

inline ll read() {
  ll x = 0, f = 1;
  char ch = getchar();
  while (ch < '0' || ch > '9') {
    if (ch == '-') f = -1;
    ch = getchar();
  }
  while (ch <= '9' && ch >= '0') {
    x = 10 * x + ch - '0';
    ch = getchar();
  }
  return x * f;
}

const int mod = 1004535809;
const int GEN = 3;

namespace poly {
  int alpha[2][400400], rev[400400], ntt_lst = -1;
  inline int sum(int x, int y) {
    x += y;
    return x >= mod ? x - mod : x;
  }
  inline int sub(int x, int y) {
    x -= y;
    return x < 0 ? x + mod : x;
  }
  inline int ksm(int x, int p) {
    int ret = 1;
    while (p) {
      if (p & 1) ret = ret * 1ll * x % mod;
      x = x * 1ll * x % mod; p >>= 1;
    }
    return ret;
  }
  inline void ntt_init(int n) {
    if (ntt_lst == n) return;
    ntt_lst = n;
    alpha[0][0] = alpha[1][0] = 1;
    alpha[0][1] = ksm(GEN, (mod - 1) / n);
    alpha[1][1] = ksm(alpha[0][1], mod - 2);
    rep(i, 2, n) rep(x, 0, 1) alpha[x][i] = alpha[x][i - 1] * 1ll * alpha[x][1] % mod;
    int nw = n >> 1;
    rep(i, 1, n - 2) {
      rev[i] = nw;
      int j = n >> 1;
      while (nw >= j) {
        nw -= j;
        j >>= 1;
      }
      nw += j;
    }
  }
  inline void ntt(int *a, int n, bool f) {
    ntt_init(n);
    rep(i, 1, n - 2) if (i < rev[i]) swap(a[i], a[rev[i]]);
    for (int i = 1; i < n; i <<= 1) {
      for (int j = 0, off = n / (i << 1); j + i < n; j += (i << 1)) {
        for (int k = j, cur = 0; k < j + i; k++, cur += off) {
          int x = a[k], y = a[k + i] * 1ll * alpha[f][cur] % mod;
          a[k] = sum(x, y); a[k + i] = sub(x, y);
        }
      }
    }
    if (f) {
      int x = ksm(n, mod - 2);
      rep(i, 0, n - 1) a[i] = a[i] * 1ll * x % mod;
    }
  }
  inline void mul(int *a, int *b, int n) {
    ntt(a, n, false);
    ntt(b, n, false);
    rep(i, 0, n - 1) a[i] = a[i] * 1ll * b[i] % mod;
    ntt(a, n, true);
  }
  inline void mul(int *a, int n, int *b, int m, int *res, int mx_len = -1) {
    static int A[400400], B[400400];
    if (mx_len == -1) mx_len = n + m;
    n = min(n, mx_len); m = min(m, mx_len);
    int len = 1;
    while (len < n + m) len <<= 1;
    rep(i, 0, len - 1) {
      A[i] = i < n ? a[i] : 0;
      B[i] = i < m ? b[i] : 0;
    }
    mul(A, B, len);
    memcpy(res, A, mx_len << 2);
  }
}

const int maxn = 400400;
int n, m, s;
int w[maxn], a[maxn], b[maxn], f[maxn];
int fac[10000100], inv[10000100];

inline int C(int x, int y) {
  return fac[x] * 1ll * inv[y] % mod * inv[x - y] % mod;
}

void work() {
  n = read(), m = read(), s = read();
  rep(i, 0, m) w[i] = read();
  int lim = min(n / s, m), up = max(n, m);
  fac[0] = 1;
  rep(i, 1, up) fac[i] = fac[i - 1] * 1ll * i % mod;
  inv[up] = poly::ksm(fac[up], mod - 2);
  rrep(i, up - 1, 0) inv[i] = inv[i + 1] * 1ll * (i + 1) % mod;
  rep(i, 0, lim) {
    a[i] = w[i] * 1ll * inv[i] % mod;
    if (i & 1) b[i] = (mod - inv[i]) % mod;
    else b[i] = inv[i] % mod;
  }
  poly::mul(a, lim + 1, b, lim + 1, a);
  rep(i, 0, lim) f[i] = fac[i] * 1ll * a[i] % mod;
  int ans = 0;
  rep(i, 0, lim) {
    int nw = C(n, i * s) * 1ll * C(m, i) % mod * fac[i * s] % mod * poly::ksm(inv[s], i) % mod * f[i] % mod * poly::ksm(m - i, n - i * s) % mod;
    ans = (ans + nw) % mod;
  }
  printf("%d\n", ans);
}

int main() {
  #ifdef LZT
    freopen("in", "r", stdin);
  #endif

  work();

  #ifdef LZT
    Debug("My Time: %.3lfms\n", (double)clock() / CLOCKS_PER_SEC);
  #endif
}

原文地址:https://www.cnblogs.com/wawawa8/p/10158853.html

时间: 2024-10-12 09:22:28

bzoj 5306 [HAOI2018] 染色的相关文章

BZOJ 2243: [SDOI2011]染色 树链剖分

2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1886  Solved: 752[Submit][Status] Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”.“222”和“1”. 请你写一个程序依次完成这m个操作. In

洛谷 P2486 BZOJ 2243 [SDOI2011]染色

题目描述 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”.“222”和“1”. 请你写一个程序依次完成这m个操作. 输入输出格式 输入格式: 第一行包含2个整数n和m,分别表示节点数和操作数: 第二行包含n个正整数表示n个节点的初始颜色 下面 行每行包含两个整数x和y,表示x和y之间有一条无向边. 下面 行每行描述一个操作: “C a

bzoj 4033 树上染色 - 树形动态规划

有一棵点数为N的树,树边有边权.给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑 色,并将其他的N-K个点染成白色.将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间距离的和的 收益.问收益最大值是多少. Input 第一行两个整数N,K. 接下来N-1行每行三个正整数fr,to,dis,表示该树中存在一条长度为dis的边(fr,to). 输入保证所有点之间是联通的. N<=2000,0<=K<=N Output 输出一个正整数,表示收益的最大值. Sampl

bzoj 2243: [SDOI2011]染色 线段树区间合并+树链剖分

2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 7925  Solved: 2975[Submit][Status][Discuss] Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”.“222”和“1”. 请你写一个程序依次完

[bzoj 2243]: [SDOI2011]染色 [树链剖分][线段树]

Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”.“222”和“1”. 请你写一个程序依次完成这m个操作. Input 第一行包含2个整数n和m,分别表示节点数和操作数: 第二行包含n个正整数表示n个节点的初始颜色 下面 行每行包含两个整数x和y,表示x和y之间有一条无向边. 下面 行每行描述一个操作: “C a

BZOJ 2243:染色(树链剖分+区间合并线段树)

[SDOI2011]染色Description给定一棵有n个节点的无根树和m个操作,操作有2类:1.将节点a到节点b路径上所有点都染成颜色c:2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”.“222”和“1”.请你写一个程序依次完成这m个操作.Input第一行包含2个整数n和m,分别表示节点数和操作数:第二行包含n个正整数表示n个节点的初始颜色下面 行每行包含两个整数x和y,表示x和y之间有一条无向边.下面 行每行描述一个操作:“C

BZOJ 2243: [SDOI2011]染色 树链剖分+线段树区间合并

2243: [SDOI2011]染色 Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”.“222”和“1”. 请你写一个程序依次完成这m个操作. Input 第一行包含2个整数n和m,分别表示节点数和操作数: 第二行包含n个正整数表示n个节点的初始颜色 下面 行每行包含两个整数x和y,表示x和y之间有一条无向边.

BZOJ 2243: [SDOI2011]染色 树链剖分 倍增lca 线段树

2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://www.lydsy.com/JudgeOnline/problem.php?id=2243 Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”.“222”和“1”. 请你写

HAOI2018 [HAOI2018]染色 【组合数 + 容斥 + NTT】

题目 为了报答小 C 的苹果, 小 G 打算送给热爱美术的小 C 一块画布, 这块画布可 以抽象为一个长度为 \(N\) 的序列, 每个位置都可以被染成 \(M\) 种颜色中的某一种. 然而小 C 只关心序列的 \(N\) 个位置中出现次数恰好为 \(S\) 的颜色种数, 如果恰 好出现了 \(S\) 次的颜色有 \(K\) 种, 则小 C 会产生 \(W_k\) 的愉悦度. 小 C 希望知道对于所有可能的染色方案, 他能获得的愉悦度的和对 1004535809 取模的结果是多少. 输入格式 从