大佬1:這題生成函數,比較基礎吧
大佬2:確實基礎
我:才發現只有我是零基礎
題目大意:
https://csacademy.com/ieeextreme15/task/candy-shop/statement/
有N種糖果,每種糖果有{ai包,每包含有bi塊}。問湊齊恰好K塊糖果有多少種方法?
1<=N,K<=10^5;
1<=ai,bi<=10^5;
參考題解 洛谷P4389
(注:本文代碼題解都是抄自洛谷一位大佬,幾乎非原創,咸魚只是為了做個記錄
大佬博客 https://www.cnblogs.com/PinkRabbit/p/10423084.html)
比賽的時候,毫無思路,瞄到60%的數據N,K<=1000。只能dp騙60分
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int N = 1e5 + 5;
int dp[2][N];
int main()
{
int n, K, nw, pr;
scanf("%d%d", &n, &K);
dp[0][0] = 1;
for (int i = 1, a, b; i <= n; i++)
{
scanf("%d%d", &a, &b);
nw = i & 1;
pr = !nw;
for (int j = 0; j <= K; j++)
{
dp[nw][j] = dp[pr][j];
for (int k = 1; k <= a && j - k * b >= 0; k++)
{
dp[nw][j] += dp[pr][j - k * b];
dp[nw][j] %= mod;
}
}
}
printf("%d\n", int(dp[nw][K] % mod));
return 0;
}
/*
Verdict: 56.68 points (60%)
Language: C++
CPU Time usage: 3089 ms
Memory usage: 1516 KB
最壞時間復雜度NKK。由於題目數據Ai,Bi可能是隨機分布的,所以平均時間復雜度NKlgK
*/
1.生成函數(組合數學)
假設某糖果有{a=5包,每包b=3塊}那么生成函數為
\[S1=1+x^3+x^6+x^9+x^{12}+x^{15}=(1-x^{3*(5+1)})/(1-x^3) \]
另一種糖果有{a=3包,每包b=2塊}
\[S2=1+x^2+x^4+x^6=(1-x^{2*(3+1)})/(1-x^2) \]
只用這兩種湊齊k塊的方案數,就是表達式S1*S2里頭x^k前面的系數(廢話)
等比數列求個和-->生成函數
\[(1-x^{b*(a+1)})/(1-x^b) \]
設答案的生成函數為F,則有
\[F=\prod_{i=1}^N (1-x^{b_i*({a_i}+1)})/(1-x^{b_i}) \]
2.公式推倒
兩邊取對數:
\[ln F = \sum_{i=1}^N ln(1-x^{b_i*({a_i}+1)})-ln(1-x^{b_i}) \]
又
\[ln(1-x)=-\sum_{j=1}{x^j}/j \]
故
\[lnF =\sum_{i=1}^N (\sum_{j=1}{x^{b_i*j}}/j - \sum_{j=1}{x^{b_i*({a_i}+1)*j}}/j) \]
\[F=e^{lnF} \]
至此就完事了,解出x^k的系數
3.公式部分時間復雜度
由上公式,易知每一輪j的上界為K/bi和K/(bi * (ai + 1))
雖然最壞情況下時間復雜度為NK,但由於題目數據Bi可能是隨機分布,所以估算時間復雜度為NlgK,能過掉此題。
為了把復雜度K嚴格地降到lgK,可以預先統計bi和bi(ai+1)的個數cnt,就能用如下的公式得到KlgK的復雜度
\[lnF =\sum (\sum_{j=1}{x^{b*j}}/j*cnt_b) - \sum(\sum_{j=1}{x^{b*({a}+1)*j}}/j*cnt_{b*(a+1)}) \]
AC代碼1(未預處理統計)
//代碼抄自洛谷P4389 付公主的背包
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int G = 3, iG = 332748118;
const int maxn = 1 << 18;
inline ll fpow(ll b, int e) //快速冪b^e。人菜數論只會gcd
{
ll a = 1;
for (; e; e >>= 1, b = b * b % mod)
if (e & 1)
a = a * b % mod;
return a;
}
//下面的函數就真的不知道啥玩意了,畢竟真沒學過數學
ll Inv[maxn];
inline void Init(int N) //這里可能是逆元打表
{
Inv[0] = Inv[1] = 1;
for (int i = 2; i < N; ++i)
Inv[i] = 1ll * Inv[mod % i] * (mod - mod / i) % mod;
}
int Sz, R[maxn];
ll InvSz;
inline void InitFNTT(int N) //初始化快速數論變換?
{
int Bt = 0;
for (; 1 << Bt < N; ++Bt)
;
if (Sz == (1 << Bt))
return;
Sz = 1 << Bt;
InvSz = -(mod - 1) / Sz;
for (int i = 1; i < Sz; ++i)
R[i] = R[i >> 1] >> 1 | (i & 1) << (Bt - 1);
}
inline void FNTT(ll *A, int Ty) //快速數論變換,確實不會啊
{
for (int i = 0; i < Sz; ++i)
if (R[i] < i)
swap(A[R[i]], A[i]);
for (int j = 1, j2 = 2; j < Sz; j <<= 1, j2 <<= 1)
{
ll gn = fpow(~Ty ? G : iG, (mod - 1) / j2), g, X, Y;
for (int i = 0, k; i < Sz; i += j2)
{
for (k = 0, g = 1; k < j; ++k, g = g * gn % mod)
{
X = A[i + k], Y = g * A[i + j + k] % mod;
A[i + k] = (X + Y) % mod, A[i + j + k] = (X - Y) % mod;
}
}
}
if (!~Ty)
for (int i = 0; i < Sz; ++i)
A[i] = A[i] * InvSz % mod;
}
inline void PolyInv(ll *A, int N, ll *B) //這里可能是多項式求逆
{
static ll tA[maxn], tB[maxn];
B[0] = fpow(A[0], mod - 2);
for (int L = 1; L < N; L <<= 1)
{
int L2 = L << 1, L4 = L << 2;
InitFNTT(L4);
memcpy(tA, A, 8 * L2);
memset(tA + L2, 0, 8 * (Sz - L2));
memcpy(tB, B, 8 * L);
memset(tB + L, 0, 8 * (Sz - L));
FNTT(tA, 1), FNTT(tB, 1);
for (int i = 0; i < Sz; ++i)
tB[i] = (2 - tB[i] * tA[i]) % mod * tB[i] % mod;
FNTT(tB, -1);
for (int i = 0; i < L2; ++i)
B[i] = tB[i];
}
}
inline void PolyLn(ll *A, int N, ll *B) //顧名思義ba
{
static ll tA[maxn], tB[maxn];
PolyInv(A, N - 1, tB);
InitFNTT(N * 2 - 3);
for (int i = 1; i < N; ++i)
tA[i - 1] = i * A[i] % mod;
memset(tA + N - 1, 0, 8 * (Sz - N + 1));
memset(tB + N - 1, 0, 8 * (Sz - N + 1));
FNTT(tA, 1), FNTT(tB, 1);
for (int i = 0; i < Sz; ++i)
tA[i] = tA[i] * tB[i] % mod;
FNTT(tA, -1);
B[0] = 0;
for (int i = 1; i < N; ++i)
B[i] = tA[i - 1] * Inv[i] % mod;
}
inline void PolyExp(ll *A, int N, ll *B) //多項式exp
{
static ll tA[maxn], tB[maxn];
B[0] = 1;
for (int L = 1; L < N; L <<= 1)
{
int L2 = L << 1, L4 = L << 2;
memset(B + L, 0, 8 * (L2 - L));
PolyLn(B, L2, tB);
InitFNTT(L4);
memcpy(tA, B, 8 * L);
memset(tA + L, 0, 8 * (Sz - L));
for (int i = 0; i < L2; ++i)
tB[i] = ((!i) - tB[i] + A[i]) % mod;
memset(tB + L2, 0, 8 * (Sz - L2));
FNTT(tA, 1), FNTT(tB, 1);
for (int i = 0; i < Sz; ++i)
tA[i] = tA[i] * tB[i] % mod;
FNTT(tA, -1);
for (int i = 0; i < L2; ++i)
B[i] = tA[i];
}
}
///各種奇怪又十分基礎的函數終於過了,都快絕望了..ToT
int N, K;
ll A[maxn], B[maxn];
int main()
{
scanf("%d%d", &N, &K);
Init(maxn);
//公式部分==如果輸入是隨機數據那么平均復雜度為nlgk
for (int i = 1; i <= N; ++i)
{
int ai, bi;
scanf("%d%d", &ai, &bi);
for (ll j = 1; j <= K / bi; ++j)
{
A[bi * j] = (A[bi * j] + Inv[j]) % mod;
}
ll val = bi * (ai + 1ll);
for (ll j = 1; j <= K / val; ++j)
{
A[val * j] = (A[val * j] - Inv[j] + mod) % mod;
}
}
//
PolyExp(A, K + 1, B);
cout << (B[K] + mod) % mod << endl;
return 0;
}
// Verdict: 100 points
// Language: C++
// CPU Time usage: 2642 ms
// Memory usage: 17.1 MB
AC代碼2(公式復雜度降到KlgK)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int G = 3, iG = 332748118;
const int MS = 1 << 18;
inline ll qPow(ll b, int e)
{
ll a = 1;
for (; e; e >>= 1, b = b * b % mod)
if (e & 1)
a = a * b % mod;
return a;
}
ll Inv[MS];
inline void Init(int N)
{
Inv[0] = Inv[1] = 1;
for (int i = 2; i < N; ++i)
Inv[i] = 1ll * Inv[mod % i] * (mod - mod / i) % mod;
}
int Sz, R[MS];
ll InvSz;
inline void InitFNTT(int N)
{
int Bt = 0;
for (; 1 << Bt < N; ++Bt)
;
if (Sz == (1 << Bt))
return;
Sz = 1 << Bt;
InvSz = -(mod - 1) / Sz;
for (int i = 1; i < Sz; ++i)
R[i] = R[i >> 1] >> 1 | (i & 1) << (Bt - 1);
}
inline void FNTT(ll *A, int Ty)
{
for (int i = 0; i < Sz; ++i)
if (R[i] < i)
swap(A[R[i]], A[i]);
for (int j = 1, j2 = 2; j < Sz; j <<= 1, j2 <<= 1)
{
ll gn = qPow(~Ty ? G : iG, (mod - 1) / j2), g, X, Y;
for (int i = 0, k; i < Sz; i += j2)
{
for (k = 0, g = 1; k < j; ++k, g = g * gn % mod)
{
X = A[i + k], Y = g * A[i + j + k] % mod;
A[i + k] = (X + Y) % mod, A[i + j + k] = (X - Y) % mod;
}
}
}
if (!~Ty)
for (int i = 0; i < Sz; ++i)
A[i] = A[i] * InvSz % mod;
}
inline void PolyInv(ll *A, int N, ll *B)
{
static ll tA[MS], tB[MS];
B[0] = qPow(A[0], mod - 2);
for (int L = 1; L < N; L <<= 1)
{
int L2 = L << 1, L4 = L << 2;
InitFNTT(L4);
memcpy(tA, A, 8 * L2);
memset(tA + L2, 0, 8 * (Sz - L2));
memcpy(tB, B, 8 * L);
memset(tB + L, 0, 8 * (Sz - L));
FNTT(tA, 1), FNTT(tB, 1);
for (int i = 0; i < Sz; ++i)
tB[i] = (2 - tB[i] * tA[i]) % mod * tB[i] % mod;
FNTT(tB, -1);
for (int i = 0; i < L2; ++i)
B[i] = tB[i];
}
}
inline void PolyLn(ll *A, int N, ll *B)
{
static ll tA[MS], tB[MS];
PolyInv(A, N - 1, tB);
InitFNTT(N * 2 - 3);
for (int i = 1; i < N; ++i)
tA[i - 1] = i * A[i] % mod;
memset(tA + N - 1, 0, 8 * (Sz - N + 1));
memset(tB + N - 1, 0, 8 * (Sz - N + 1));
FNTT(tA, 1), FNTT(tB, 1);
for (int i = 0; i < Sz; ++i)
tA[i] = tA[i] * tB[i] % mod;
FNTT(tA, -1);
B[0] = 0;
for (int i = 1; i < N; ++i)
B[i] = tA[i - 1] * Inv[i] % mod;
}
inline void PolyExp(ll *A, int N, ll *B)
{
static ll tA[MS], tB[MS];
B[0] = 1;
for (int L = 1; L < N; L <<= 1)
{
int L2 = L << 1, L4 = L << 2;
memset(B + L, 0, 8 * (L2 - L));
PolyLn(B, L2, tB);
InitFNTT(L4);
memcpy(tA, B, 8 * L);
memset(tA + L, 0, 8 * (Sz - L));
for (int i = 0; i < L2; ++i)
tB[i] = ((!i) - tB[i] + A[i]) % mod;
memset(tB + L2, 0, 8 * (Sz - L2));
FNTT(tA, 1), FNTT(tB, 1);
for (int i = 0; i < Sz; ++i)
tA[i] = tA[i] * tB[i] % mod;
FNTT(tA, -1);
for (int i = 0; i < L2; ++i)
B[i] = tA[i];
}
}
int N, K;
ll A[MS], B[MS];
map<int, int> counter;
int main()
{
scanf("%d%d", &N, &K);
Init(MS);
for (int i = 1, ai, bi; i <= N; ++i)
{
scanf("%d%d", &ai, &bi);
if (bi <= K)
{
counter[bi]++;
}
ll val = bi * (ai + 1ll);
if (val <= K)
{
counter[val]--;
}
}
///公式部分==把復雜度降到嚴格klgk
for (map<int, int>::iterator it = counter.begin(); it != counter.end(); it++)
{
ll val = it->first, cnt = it->second;
for (ll j = 1; j <= K / val; ++j)
{
A[val * j] = (A[val * j] + cnt * Inv[j]) % mod;
}
}
//
PolyExp(A, K + 1, B);
cout << (B[K] + mod) % mod << endl;
return 0;
}
/*
Verdict: 100 points
Language: C++
CPU Time usage: 534 ms
Memory usage: 19.9 MB
*/