传送门


调了1h竟然是因为1004535809写成了998244353

“恰好有\(K\)种颜色出现了\(S\)次”的限制似乎并不容易达到,考虑容斥计算。

令\(c_j\)表示强制\(j\)种颜色恰好出现\(S\)次,其他颜色随意染的方案数。可以通过生成函数知道

\(\begin{align*} c_j &= \binom{m}{j} n! [x^n] (\frac{x^k}{k!})^j (\sum\limits_{i=0}^\infty \frac{x^i}{i!})^{m-j} \\ &= \binom{m}{j} n! [x^n] (\frac{x^k}{k!})^j e^{(m-j)x} \\ &= \binom{m}{j} n! [x^n] (\frac{x^k}{k!})^j \sum\limits_{i=0}^\infty \frac{x^i (m-j)^i}{i!} \\ &= \binom{m}{j}n! \frac{(m-j)^{n-jk}}{(k!)^j (n-jk)!} \end{align*}\)

显然是可以预处理阶乘之后\(O(1)\)计算的。注意不要漏掉了指数型生成函数前面要乘的\(n!\)。

又设\(h_j\)表示恰好有\(j\)种颜色出现了\(S\)次的方案总数。不难发现有一个反演:\(h_j = c_j - \sum\limits_{i=j+1}^{Max} h_{i}A(i,j)\),\(A(i,j)\)是一个与\(i,j\)相关的系数,表示\(h_i\)在\(c_j\)中的出现次数。

既然\(h_i\)中恰好有\(i\)种颜色出现了\(S\)次,那么对于任意一个对\(h_i\)产生贡献的状态,只要枚举到当前状态中\(i\)种恰好出现了\(S\)次的颜色构成的集合的任意一个大小为\(j\)的子集时都会对\(c_j\)产生\(1\)的贡献。所以\(A(i,j) = \binom{i}{j}\)

所以可以得到\(h_j = c_j - \sum\limits_{i=j+1}^{Max}h_i \binom{i}{j}\),两边同乘\(j!\)得到\(h_jj! = c_jj! - \sum\limits_{i=j+1}^{Max}\frac{h_ii!}{(i-j)!}\)

设多项式\(H = \sum\limits_{i=0}^{Max}h_ii!x^i , C = \sum\limits_{i=0}^{Max}c_ii!x^i\),记\(rev(H)\)为多项式\(H\)所有系数翻转过来之后的多项式,那么不难得到\(rev(H) = rev(C) - W * rev(H)\),其中\(W = \sum\limits_{i=1}^{Max} \frac{1}{i!}x^i\)。多项式求逆即可。

Update:不难发现\(W+1 = e^x\),所以求逆的结果是\(e^{-x}\),所以可以不必求逆直接把\(e^{-x}\)的系数代替求逆;实际上这个反演的过程是二项式反演的一个变体,可以通过二项式反演的方式进行NTT,实质一样。

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<random>
  4. #include<cstring>
  5. #include<algorithm>
  6. //This code is written by Itst
  7. using namespace std;
  8. const int mod = 998244353;
  9. inline int read(bool flg = 0){
  10. int a = 0;
  11. char c = getchar();
  12. bool f = 0;
  13. while(!isdigit(c) && c != EOF){
  14. if(c == '-')
  15. f = 1;
  16. c = getchar();
  17. }
  18. if(c == EOF)
  19. exit(0);
  20. while(isdigit(c)){
  21. if(flg)
  22. a = (a * 10ll + c - 48) % mod;
  23. else
  24. a = a * 10 + c - 48;
  25. c = getchar();
  26. }
  27. if(flg) a += mod;
  28. return f ? -a : a;
  29. }
  30. const int MAXN = (1 << 19) + 7 , MAXM = 1e7 + 7 , MOD = 1004535809;
  31. #define PII pair < int , int >
  32. #define st first
  33. #define nd second
  34. inline int poww(long long a , int b){
  35. int times = 1;
  36. while(b){
  37. if(b & 1)
  38. times = times * a % MOD;
  39. a = a * a % MOD;
  40. b >>= 1;
  41. }
  42. return times;
  43. }
  44. namespace poly{
  45. const int G = 3 , INV = (MOD + 1) / G;
  46. int A[MAXN] , B[MAXN] , C[MAXN] , D[MAXN] , E[MAXN];
  47. int a[MAXN] , b[MAXN] , c[MAXN] , d[MAXN];
  48. int need , inv , dir[MAXN] , _inv[MAXN];
  49. #define clear(x) memset(x , 0 , sizeof(int) * need)
  50. void init(int len){
  51. need = 1;
  52. while(need < len)
  53. need <<= 1;
  54. inv = poww(need , MOD - 2);
  55. for(int i = 1 ; i < need ; ++i)
  56. dir[i] = (dir[i >> 1] >> 1) | (i & 1 ? need >> 1 : 0);
  57. }
  58. void init_inv(){
  59. _inv[1] = 1;
  60. for(int i = 2 ; i < MAXN ; ++i)
  61. _inv[i] = MOD - 1ll * (MOD / i) * _inv[MOD % i] % MOD;
  62. }
  63. void NTT(int *arr , int type){
  64. for(int i = 1 ; i < need ; ++i)
  65. if(i < dir[i])
  66. arr[i] ^= arr[dir[i]] ^= arr[i] ^= arr[dir[i]];
  67. for(int i = 1 ; i < need ; i <<= 1){
  68. int wn = poww(type == 1 ? G : INV , (MOD - 1) / i / 2);
  69. for(int j = 0 ; j < need ; j += i << 1){
  70. long long w = 1;
  71. for(int k = 0 ; k < i ; ++k , w = w * wn % MOD){
  72. int x = arr[j + k] , y = arr[i + j + k] * w % MOD;
  73. arr[j + k] = x + y >= MOD ? x + y - MOD : x + y;
  74. arr[i + j + k] = x < y ? x + MOD - y : x - y;
  75. }
  76. }
  77. }
  78. }
  79. void mul(int *a , int *b){
  80. NTT(a , 1);NTT(b , 1);
  81. for(int i = 0 ; i < need ; ++i)
  82. a[i] = 1ll * a[i] * b[i] % MOD;
  83. NTT(a , -1);
  84. }
  85. void getInv(int *a , int *b , int len){
  86. if(len == 1){
  87. b[0] = poww(a[0] , MOD - 2);
  88. return;
  89. }
  90. getInv(a , b , (len + 1) >> 1);
  91. memcpy(A , a , sizeof(int) * len);
  92. memcpy(B , b , sizeof(int) * len);
  93. init(len * 3);
  94. NTT(A , 1);NTT(B , 1);
  95. for(int i = 0 ; i < need ; ++i)
  96. A[i] = 1ll * A[i] * B[i] % MOD * B[i] % MOD;
  97. NTT(A , -1);
  98. for(int i = 0 ; i < len ; ++i)
  99. b[i] = (2 * b[i] - 1ll * A[i] * inv % MOD + MOD) % MOD;
  100. clear(A);clear(B);
  101. }
  102. }
  103. using namespace poly;
  104. int F[MAXN] , H[MAXN] , jc[MAXM] , Inv[MAXM] , W[MAXN];
  105. int N , M , K , Len;
  106. void init(){
  107. jc[0] = 1;
  108. for(int i = 1 ; i <= N || i <= M ; ++i)
  109. jc[i] = 1ll * jc[i - 1] * i % MOD;
  110. Inv[max(N , M)] = poww(jc[max(N , M)] , MOD - 2);
  111. for(int i = max(N , M) - 1 ; i >= 0 ; --i)
  112. Inv[i] = Inv[i + 1] * (i + 1ll) % MOD;
  113. }
  114. int binom(int b , int a){
  115. return b < a ? 0 : 1ll * jc[b] * Inv[a] % MOD * Inv[b - a] % MOD;
  116. }
  117. int calc(int j){
  118. return 1ll * poww(Inv[K] , j) * Inv[N - j * K] % MOD * poww(M - j , N - j * K) % MOD * binom(M , j) % MOD * jc[N] % MOD;
  119. }
  120. int main(){
  121. #ifndef ONLINE_JUDGE
  122. freopen("in","r",stdin);
  123. //freopen("out","w",stdout);
  124. #endif
  125. init_inv();
  126. N = read(); M = read(); K = read();
  127. for(int i = 0 ; i <= M ; ++i) W[i] = read();
  128. Len = min(N / K , M);
  129. init();
  130. for(int i = 0 ; i <= Len ; ++i)
  131. F[i] = 1ll * calc(i) * jc[i] % MOD;
  132. reverse(F , F + Len + 1);
  133. for(int i = 0 ; i <= Len ; ++i)
  134. H[i] = Inv[i];
  135. getInv(H , a , Len + 1);
  136. init((Len + 1) * 2);
  137. mul(F , a);
  138. reverse(F , F + Len + 1);
  139. int ans = 0;
  140. for(int i = 0 ; i <= Len ; ++i)
  141. ans = (ans + 1ll * F[i] * inv % MOD * Inv[i] % MOD * W[i]) % MOD;
  142. cout << ans;
  143. return 0;
  144. }

LOJ2527 HAOI2018 染色 容斥、生成函数、多项式求逆的更多相关文章

  1. 【XSY2612】Comb Avoiding Trees 生成函数 多项式求逆 矩阵快速幂

    题目大意 本题的满二叉树定义为:不存在只有一个儿子的节点的二叉树. 定义一棵满二叉树\(A\)包含满二叉树\(B\)当且经当\(A\)可以通过下列三种操作变成\(B\): 把一个节点的两个儿子同时删掉 ...

  2. 2019.01.01 bzoj3625:小朋友和二叉树(生成函数+多项式求逆+多项式开方)

    传送门 codeforces传送门codeforces传送门codeforces传送门 生成函数好题. 卡场差评至今未过 题意简述:nnn个点的二叉树,每个点的权值KaTeX parse error: ...

  3. Luogu5162 WD与积木(生成函数+多项式求逆)

    显然的做法是求出斯特林数,但没有什么优化空间. 考虑一种暴力dp,即设f[i]为i块积木的所有方案层数之和,g[i]为i块积木的方案数.转移时枚举第一层是哪些积木,于是有f[i]=g[i]+ΣC(i, ...

  4. 【BZOJ3625】【codeforces438E】小朋友和二叉树 生成函数+多项式求逆+多项式开根

    首先,我们构造一个函数$G(x)$,若存在$k∈C$,则$[x^k]G(x)=1$. 不妨设$F(x)$为最终答案的生成函数,则$[x^n]F(x)$即为权值为$n$的神犇二叉树个数. 不难推导出,$ ...

  5. COGS 2259 异化多肽 —— 生成函数+多项式求逆

    题目:http://cogs.pro:8080/cogs/problem/problem.php?pid=2259 如果构造生成函数是许多个 \( (1+x^{k}+x^{2k}+...) \) 相乘 ...

  6. 洛谷P4721 【模板】分治 FFT(生成函数+多项式求逆)

    传送门 我是用多项式求逆做的因为分治FFT看不懂…… upd:分治FFT的看这里 话说这个万恶的生成函数到底是什么东西…… 我们令$F(x)=\sum_{i=0}^\infty f_ix^i,G(x) ...

  7. 牛客IOI周赛17-提高组 卷积 生成函数 多项式求逆 数列通项公式

    LINK:卷积 思考的时候 非常的片面 导致这道题没有推出来. 虽然想到了设生成函数 G(x)表示最后的答案的普通型生成函数 不过忘了化简 GG. 容易推出 \(G(x)=\frac{F(x)}{1- ...

  8. 洛谷P4841 城市规划(生成函数 多项式求逆)

    题意 链接 Sol Orz yyb 一开始想的是直接设\(f_i\)表示\(i\)个点的无向联通图个数,枚举最后一个联通块转移,发现有一种情况转移不到... 正解是先设\(g(n)\)表示\(n\)个 ...

  9. P4491 [HAOI2018]染色 容斥+NTT

    $ \color{#0066ff}{ 题目描述 }$ 为了报答小 C 的苹果, 小 G 打算送给热爱美术的小 C 一块画布, 这块画布可 以抽象为一个长度为 \(N\) 的序列, 每个位置都可以被染成 ...

随机推荐

  1. Spring学习之旅(七)基于XML配置与基于AspectJ注解配置的AOP编程比较

    本篇博文用一个稍复杂点的案例来对比一下基于XML配置与基于AspectJ注解配置的AOP编程的不同. 相关引入包等Spring  AOP编程准备,请参考小编的其他博文,这里不再赘述. 案例要求: 写一 ...

  2. 【转载】java架构师进阶之路

    Java架构师,应该算是一些Java程序员们的一个职业目标了吧.很多码农码了五六年的代码也没能成为架构师.那成为Java架构师要掌握哪些技术呢,总体来说呢,有两方面,一个是基础技术,另一个就是组织能力 ...

  3. Mybatis使用动态代理实现拦截器功能

    1.背景介绍 拦截器顾名思义为拦截某个功能的一个武器,在众多框架中均有“拦截器”.这个Plugin有什么用呢?或者说拦截器有什么用呢?可以想想拦截器是怎么实现的.Plugin用到了Java中很重要的一 ...

  4. Android为TV端助力 handler ,message消息发送方式

    1.Message msg =  Message.obtain(mainHandler) msg.obj=obj;//添加你需要附加上去的内容 msg.what = what;//what消息处理的类 ...

  5. X100S Collection Before 2014/08/01

    风暴前的东京湾 // Tokyo Bay before Storm 上野公园 // Ueno Park

  6. <解决方法>Centos安装使用Chromedriver

    一.安装Chrome 我安装好Centos系统后,就在网上去找Chrome浏览器的安装方法,使用过yum,rpm都安装不上,会报错,然后询问公司的运维,他给我了个包,然后使用:yum localins ...

  7. 通过UNIX域套接字传递描述符的应用

      传送文件描述符是高并发网络服务编程的一种常见实现方式.Nebula 高性能通用网络框架即采用了UNIX域套接字传递文件描述符设计和实现.本文详细说明一下传送文件描述符的应用. 1. TCP服务器程 ...

  8. python之模块使用

    1.入口 """ 模块测试入口 """ import show_message as sm # 导入方式一 sm.show(sm.__nam ...

  9. Bresenham算法的实现思路

    条件已知两个点的坐标p1(x0,y0),p2(x1,y1)要求画出这条直线 之后的e代表每次的误差积累,初始值为0,可以计算出斜率为k=dy/dx=(y0-y1)/(x0-x1) 1.x为阶跃步长(直 ...

  10. 安卓程序中手机后退键与标题栏后退键是不同的,前者回出发onBackPressed()函数,后者需要重重写temclick函数

    安卓程序中手机后退键与标题栏后退键是不同的,前者回出发onBackPressed()函数,后者需要重重写temclick函数