luogu4345 [SHOI2015]超能粒子炮·改
link
輸入\(n,k\),求\(\sum_{i=0}^k{n\choose i}\)對2333取模,10萬組詢問,n,k<=1e18
註意到一個2333這個數字很小並且還是質數這一良好性質,我們可以根據Lucas定理優化式子
為了方便,令\(p=2333\)
設\(f(n,k)=\sum_{i=0}^k{n\choose i}\)
對於\(i\in[0,p\lfloor\frac kp\rfloor)\),根據lucas定理有\({n\choose i}={n\%p\choose i\%p}{n/p\choose i/p}\)
對於每一對\((i\%p,i/p)\)都能惟一確定一個\(i\),根據乘法原理有
\(f(n\%p,p-1)*f(\lfloor\frac np\rfloor,\lfloor\frac kp\rfloor-1)\)
對於\(i\in[p\lfloor\frac kp\rfloor,k]\)則它們/p的值相同,根據Lucas定理可以化為\({\lfloor\frac np\rfloor\choose\lfloor\frac kp\rfloor}*f(n\%p,k\%p)\)
所以\(f(n,k)=f(n\%p,p-1)*f(\lfloor\frac np\rfloor,\lfloor\frac kp\rfloor-1)+{\lfloor\frac np\rfloor\choose\lfloor\frac kp\rfloor}*f(n\%p,k\%p)\)
先預處理0~p-1階乘及其逆元,0~p-1裏的組合數可以O(1)
註意到在f的遞推式中頻繁用到了0~p-1內的f值,所以先O(p^2)處理這些f
那麽時間復雜度遞推式就是\(T(n,k)=T(\lfloor\frac np\rfloor,\lfloor\frac kp\rfloor)+\log p\),如果nk同階,復雜度\(O(T\log n\log ^2p)\)好像是
一開始復雜度寫錯了,最後6個點狂T。。。
代碼
#include <cstdio> using namespace std; const int p = 2333; int fac[3000], inv[3000]; int f[3000][3000]; int qpow(int x, int y) { int res = 1; for (x %= p; y > 0; x = x * x % p, y >>= 1) if (y & 1) res = res * x % p; return res; } int c(long long n, long long m) { if (n < m || m < 0) return 0; if (n < p && m < p) return fac[n] * inv[m] % p * inv[n - m] % p; return c(n / p, m / p) * c(n % p, m % p) % p; } int work(long long n, long long k) { if (n < p && k < p) return f[n][k]; return (c(n / p, k / p) * work(n % p, k % p) + work(n % p, p - 1) * work(n / p, k / p - 1)) % p; } int main() { fac[0] = 1; for (int i = 1; i < p; i++) fac[i] = fac[i - 1] * i % p; inv[p - 1] = qpow(fac[p - 1], p - 2); for (int i = p - 1; i >= 1; i--) inv[i - 1] = inv[i] * i % p; for (int i = 0; i < p; i++) { f[i][0] = c(i, 0); for (int j = 1; j < p; j++) f[i][j] = (f[i][j - 1] + c(i, j)) % p; } int t; scanf("%d", &t); while (t --> 0) { long long x, y; scanf("%lld%lld", &x, &y); printf("%d\n", work(x, y)); } return 0; }
WA了好幾發,define int long long過了後來發現計算C時候三個數乘一起就炸int了。。。
luogu4345 [SHOI2015]超能粒子炮·改