20221015_T2B+_推柿子
题意
定义函数 \(F(x)=\prod\limits_{i=0}^x \binom{x}{i}\),\(G(x)\) 为 \(F(x)\) 的因数和,求 \(\sum\limits_{i=1}^N G(i)\)
题解
赛时得分 50/100
(想到了类似正解,当时没有想着筛素数,能卡到 80 pts)
首先,我们可以想到因数和的计算公式。假如说 \(F(x)=p_1^{k_1}p_2^{k_2}p_3^{k_3}...p_n^{k_n}\),\(G(x)=\Big(\sum_{i=0}^{k_1}p_1^{i} \Big)\Big(\sum_{i=0}^{k_2}p_2^i \Big)...\Big(\sum_{i=0}^{k_n}p_n^i \Big)\)
这样的话我们可以选择把 \(F(x)\) 的因数情况分解出来。
经过一阵简单的推柿子,我们能知道 \(F(x)=\frac{(n!)^{n+1}}{(\prod_{i=1}^ni!)^2}\),然后我们只需要考虑素数上的指数就好了。
我原来的方法是因为要每个 \(G\) 都求,所以就直接枚举 \(G\) 里的 \(i\),然后枚举倍数等等,复杂度也是 \(\mathcal{O}(n^2)\) 的,只不过常数非常大。
但是有一种常数很小的办法,不去枚举 \(i\) 因为指数是具有前缀性质的。所以直接枚举素数 \(p\),然后后面记录指数的修改情况。
代码
被注释的是那种常数很大方法,只能获得 \(80\) 分,不想卡常了。
#include<bits/stdc++.h>
using namespace std;
template<typename T> inline void read(T &x) {
x = 0; register char ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) x = x * 10 + ch - '0', ch = getchar();
}
template<typename T, typename ...Args> inline void read(T &x, Args &...args) {read(x); read(args...);}
const int N = 2e5 + 10, qwq = 2e7 + 10;
typedef long long ll;
int n, P;
int prime[N], cnt0;
bool notprime[N];
void intt(int T) {
notprime[1] = 1;
for(int i = 2; i <= T; ++i) {
if(!notprime[i]) prime[++cnt0] = i;
for(int j = 1; j <= cnt0; ++j) {
int t = prime[j] * i;
if(t > T) break;
notprime[t] = 1;
if(i % prime[j] == 0) break;
}
}
}
inline ll qpow(ll x, int y) {
ll res = 1;
while(y) {
if(y & 1) res = 1ll * res * x % P;
x = 1ll * x * x % P;
y >>= 1;
}
return res;
}
ll cnt[N], ans[N];
inline int calc(int x, int y) { // log
int res = 0;
while(x % y == 0) res++, x /= y;
return res;
}
int power[qwq], pre[qwq], tot;
int Calc(int p, int c) {
while(tot < c) {
++tot; power[tot] = 1ll * power[tot - 1] * p % P;
pre[tot] = (pre[tot - 1] + power[tot]);
if(pre[tot] >= P) pre[tot] -= P;
}
return pre[c];
}
signed main() {
freopen("gonna.in", "r", stdin);
freopen("gonna.out", "w", stdout);
read(n, P);
intt(20000);
// for(int i = 1; i <= n; ++i) {
// ll res = 1;
// for(int j = 2; j <= i; ++j) cnt[j] = 2 * j - i - 1;
// for(register int k = 1; prime[k] <= i; ++k) {
// int y = prime[k];
// for(register int j = y * 2; j <= i; j += y) cnt[y] += calc(j, y, k) * cnt[j]; // O(n)
// res = 1ll * res * (qpow(y, cnt[y] + 1) - 1) % P * qpow(y - 1, P - 2) % P;
// }
// ans = (ans + res) % P;
// }
ll now = 0, sum = 0;
for(int i = 1; i <= n; ++i) ans[i] = 1;
for(int i = 1; prime[i] <= n && i <= cnt0; ++i) {
int p = prime[i]; now = sum = 0;
tot = 0; power[0] = pre[0] = 1;
for(int j = 1; j <= n; ++j) {
now += calc(j, p); sum += now;
int pd = Calc(p, now * (j + 1) - 2 * sum);
ans[j] = 1ll * ans[j] * pd % P;
}
}
ll tans = 0;
for(int i = 1; i <= n; ++i) tans += ans[i], tans %= P;
cout << tans << endl;
return 0;
}