洛谷·[HAOI2018]染色

初见安~这里是传送门:洛谷P4491 染色

题解

这个题可真是……舒适极了。

第一眼我双又没有找到多项式在哪里……因为我写出来了:设g(x)表示有x种颜色出现了s次时的情况数【答案就是情况数乘贡献的和,所以我们把情况数搞出来就行了】,然后列出方程:

\small g(i)=C_m^i(m-i)^{n-si}*\frac{(si)!}{(s!)^i}*C_n^{si}

其含义:在m种颜色里面选i种,固定出现s次,这样的话另外的n-si个位置每个位置都有m-i种颜色还可以选;对于当前si个位置,我们有计算排列数的公式形如:\frac{sum!}{a_1!a_2!...a_n!}【表示:总和的阶乘除以每种物品的数量的阶乘,前提条件是同种物品没有差别的情况下】,最后再是看这si个位置放哪里。

如果真是这样这个题就不可能黑了。因为很明显有个问题——我无法保证另外的n-si个位置里面不存在某种颜色也出现s次。所以这个g(x)的真实含义应该是:至少有x种颜色出现了s次时的情况数

这个格式是不是很眼熟?我们可以套广义容斥定理了啊!!!f(x)表示刚好有x种颜色出现s次时的情况数,那么就有:

f(k)=\sum_{i=k}^m(-1)^{i-k}C_i^k*g(i)

所以至此,暴力算的复杂度就是O(n^2)的,我们还要再化简。因为f看起来已经没有什么化简空间了,所以我们把g也带进来:

至此,就已经是最简了。我们来观察式子里面的变量项:有(i-k)和i。看起来有一丢丢卷积的味道。我们先这样构造起来看看:

明显i-k+i并不能卷起来,那我们如果翻转其中一个多项式呢?比如——我们把B翻转一下:

【因为枚举的满足条件的颜色数量可以有0~m种,所以A和B都从0开始】

现在i-k+m-i=m-k可以卷起来了呢,但是我们这个求和并不是从0或者1开始的呀。所以我们还要整体往下平移k:

【平移方法:A和B中原本的i用i+k替换。】

这样就是一个完美的卷积形式了。所以将f卷起来过后扔给贡献去算就行了。至此其实就可以去写代码啦。

可能你在写的时候会遇到一个边界的问题——就是A和B要预处理多少项呢?其实因为题目并没有保证是否一定可以有m种颜色同时出现的情况,所以明显在A(i)当中,受n-si的限制不一定能取到m+1个i的值,并且在卷积的形式以及公式的含义下,B也同样受此限制。所以我们要预处理一个limit作为A和B的项数而不一定是m。

最后——上代码:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define int long long
#define maxn 300005
#define maxm 10000007
using namespace std;
typedef long long ll;
const int mod = 1004535809;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

int len = 1, l = 0, r[maxn];
ll pw(ll a, ll b) {ll ans = 1; while(b) {if(b & 1) ans = ans * a % mod; a = a * a % mod; b >>= 1;} return ans;}
void NTT(ll *c, int flag) {
    for(int i = 1; i <= len; i++) if(i < r[i]) swap(c[i], c[r[i]]);
    for(int mid = 1; mid < len; mid <<= 1) {
        ll gn = pw(3, (mod - 1) / (mid << 1));
        if(flag == -1) gn = pw(gn, mod - 2);
        for(int L = mid << 1, ls = 0; ls < len; ls += L) {
            ll g = 1;
            for(int k = 0; k < mid; k++, g = g * gn % mod) {
                ll tmp = g * c[ls + k + mid] % mod;
                c[ls + k + mid] = (c[ls + k] - tmp + mod) % mod;
                c[ls + k] = (c[ls + k] + tmp) % mod;
            }
        }
    }
    ll rev = pw(len, mod - 2);
    if(flag == -1) for(int i = 0; i <= len; i++) c[i] = c[i] * rev % mod;
}

int n, m, s, mx;
ll w[maxn], fac[maxm], inv[maxm], A[maxn], B[maxn];
signed main() {
	n = read(), m = read(), s = read(); mx = max(n, m);
	for(int i = 0; i <= m; i++) w[i] = read();
	int lim = min(m, n / s);
	fac[0] = inv[0] = 1;
	for(int i = 1; i <= mx; i++) fac[i] = fac[i - 1] * i % mod;
	inv[mx] = pw(fac[mx], mod - 2);
	for(int i = mx - 1; i > 0; i--) inv[i] = inv[i + 1] * (i + 1) % mod;
	
	while(len <= lim + lim) len <<= 1, l++;
	for(int i = 1; i <= len; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) << l - 1);
	
	for(int i = 0, kd = 1; i <= lim; i++, kd = -kd) A[i] = (kd * inv[i] + mod) % mod;
	for(int i = 0; i <= lim; i++) //这一长串看前面的公式比较好。
        B[i] = fac[m] * inv[m - i] % mod * pw(m - i, n - s * i) % mod * fac[n] % mod * pw(inv[s], i) % mod * inv[n - s * i] % mod;
	reverse(B, B + lim + 1);//reverse一下,注意一共有lim+1项。
	NTT(A, 1), NTT(B, 1);
	for(int i = 0; i <= len; i++) A[i] = A[i] * B[i] % mod;
	NTT(A, -1);
	ll ans = 0;//应该用lim减去i,这才是卷积的\sum上面的值
	for(int i = 0; i <= lim; i++) ans = (ans + 1ll * w[i] * inv[i] % mod * A[lim - i] % mod + mod) % mod;
	printf("%lld\n", ans);
	return 0;
}

我才不会告诉你我因为最后那里的边界问题没有注意到调了一个晚上呢。

迎评:)
——End——

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值