赫斯提亚的反演篇
赫斯提亚!!/se
首先:
杜教筛
积性函数定义
对于
能够快速求出f*g的前缀和和g的前缀和即可快速求出f的前缀和
预处理前可以实现复杂度
都有
可以加速求和gcd和lcm
可以做多快啊
卷起来之后那个常数就是n了,然后剩下的是phi可以直接等于n
code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int MAXN = 4e6 + 7;
map<ll, ll> mp1, mp2;
int mu[MAXN], isp[MAXN], pri[MAXN];
ll phi[MAXN];
int N, T, tot;
inline void init() {
N = 4e6;
mu[1] = 1;
phi[1] = 1;
for(int i = 2; i <= N; ++i) {
if(!isp[i]) {
pri[++tot] = i;
phi[i] = i - 1;
mu[i] = -1;
}
for(int j = 1; j <= tot && i * pri[j] <= N; ++j) {
isp[i * pri[j]] = 1;
if(i % pri[j] == 0) {
phi[i * pri[j]] = phi[i] * pri[j];
mu[i * pri[j]] = 0;
break;
}
mu[i * pri[j]] = -mu[i];
phi[i * pri[j]] = phi[i] * (pri[j] - 1);
}
}
for(int i = 1; i <= N; ++i) {
mu[i] += mu[i - 1];
phi[i] += phi[i - 1];
}
}
inline ll solve1(ll x) {
if(x <= N)return mu[x];
if(mp1.find(x) != mp1.end())return mp1[x];
ll ans = 1;
for(ll l = 2, r; l <= x; l = r + 1) {
r = x / (x / l);
ans -= solve1(x / l) * (r - l + 1);
}
mp1[x] = ans;
return ans;
}
inline ll solve2(ll x) {
if(x <= N)return phi[x];
if(mp2.find(x) != mp2.end())return mp2[x];
ll ans = x * (x + 1) / 2;
for(ll l = 2, r; l <= x; l = r + 1) {
r = x / (x / l);
ans -= solve2(x / l) * (r - l + 1);
}
mp2[x] = ans;
return ans;
}
ll n;
int main() {
scanf("%d", &T);
init();
while(T-- > 0) {
scanf("%lld", &n);
printf("%lld %lld\n", solve2(n), solve1(n));
}
return 0;
}
P3172 [CQOI2015]选数
问题显然可以转化为从n个数里面选数,一定要包括至少一次最小的数
那么这个问题可以容斥,我们可以钦点最小的数出现一次,然后减去他出现两次,加上他出现三次....
又因为每个集合是本质相同的,所以可以用组合数加速qwq
怎么求[L,H]中有多少个数是k的倍数啊
有个上界
如果L不是k的倍数的话,否则是-1qwq
然后么得了,因为次数可能达到O(N)级别
考虑变成gcd容斥,就是表示最大公约数为i的倍数的方案数表示最大公约数为i的方案数
不难发现
不难发现
不难发现
所以说我们只需要求出一个数的最后gcd出现他的倍数的方案数
应该是这个数的倍数个数的n次幂
然后这个值难道就是了吗?
当然不是,因为我们是枚举了一个约数,这个约数只可能是H-L量级的,任何一个x大于,都不可能有两个以上的数使得他们的gcd等于x了(y和y+x一定超过了这个H,L的跨度吧)
然鹅如果我们所有数都是一样的话,这意味着我们的gcd一定会大于H-L这个范围,也就是说我们转换之后,能够保证操作序列随便给出最后的gcd都是H-L范围内的,但是如果所有数都一样就是例外,此时一定不会合法了,他们的!!,所以就是要钦定
所以
于是我们就做完了这道题/yun
注意特判k是否在L,R内,此时我们要算上一次全部相同的qwq
code:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 3e5 + 7;
const int P = 1e9 + 7;
ll f[MAXN];
ll n, k, l, h, m, ans;
inline ll ksm(ll x, ll y) {
ll ans = 1;
while(y) {
if(y & 1)ans = ans * x % P;
x = x * x % P;
y >>= 1;
}
return ans;
}
inline void solve() {
for(int i = m; i >= 1; --i) {
ll t = h / i - l / i - (l % i ? 1 : 0) + 1;
if(t <= 0)continue;
f[i] = (ksm(t, n) - t + P) % P;
// printf("%d %lld\n", t, f[i]);
for(int j = i + i; j <= m; j += i) {
f[i] += (P - f[j]) % P;
f[i] %= P;
}
}
printf("%lld\n", ans + f[1]);
return;
}
int main() {
scanf("%lld%lld%lld%lld", &n, &k, &l, &h);
ans = (k >= l && k <= h);
h /= k;
l = l / k + (l % k ? 1 : 0);
m = h - l;
// printf("%lld %lld\n", l, h);
// printf("%lld %lld\n", m, ans);
solve();
return 0;
}
杜教筛做法
这个等价于直接枚举倍数,然鹅枚举倍数要小心转换下界哦
然后我们可以考虑反演
杜教筛前面的部分,后面整除分块即可
这个整除分块有毒/吐血
code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int MAXN = 3e6 + 7;
const int P = 1e9 + 7;
map<ll, ll> mp1;
int mu[MAXN], isp[MAXN], pri[MAXN];
int N, tot;
inline ll ksm(ll x, ll y) {
ll ans = 1;
while(y) {
if(y & 1)ans = ans * x % P;
x = x * x % P;
y >>= 1;
}
return ans;
}
inline void init() {
N = 3e6;
mu[1] = 1;
for(int i = 2; i <= N; ++i) {
if(!isp[i]) {
pri[++tot] = i;
mu[i] = -1;
}
for(int j = 1; j <= tot && i * pri[j] <= N; ++j) {
isp[i * pri[j]] = 1;
if(i % pri[j] == 0) {
mu[i * pri[j]] = 0;
break;
}
mu[i * pri[j]] = -mu[i];
}
}
for(int i = 1; i <= N; ++i) {
mu[i] += mu[i - 1];
}
}
inline ll solve1(ll x) {
if(x <= N)return mu[x];
if(mp1.find(x) != mp1.end())return mp1[x];
ll ans = 1;
for(ll l = 2, r; l <= x; l = r + 1) {
r = x / (x / l);
ans -= solve1(x / l) * (r - l + 1);
}
mp1[x] = ans;
return ans;
}
ll n, k, L, H, ans;
int main() {
scanf("%lld%lld%lld%lld", &n, &k, &L, &H);
init();
L = L / k + (L % k ? 1 : 0) - 1;
H = H / k;
// printf("%lld %lld\n", L, H);
ll ans = 0;
for(int l = 1, r; l <= H; l = r + 1) {
int tmpl = L / l + 1;
int tmpr = H / l;
// printf("%d %d %d %d\n", l, r, tmpr, tmpl);
if(l <= L)r = min(H / tmpr, L / (L / l));
else r = H / tmpr;
// printf("%d %d %d %d\n", l, r, tmpr, tmpl);
ans = ans + (solve1(r) - solve1(l - 1)) * ksm(tmpr - tmpl + 1, n) % P;
ans %= P;
}
ans = (ans + P) % P;
printf("%lld\n", ans);
return 0;
}
51nod1220 约数之和
如果求约数个数和?
有一个可爱的sdoi2015反演原题
于是我们套用结论
前面的我会杜教筛
后面的?其实直接做是的
但是如果我们预处理前O(n^{2/3),那么再做还是,因为本身就是一个整除分块的形式qwq
求约数和???
我的天哪这个也可以构造
原理其实理解了之前那个构造这个也能想懂
我们不妨只看一个约数的,多个约数互相独立也就一样
之前我们对于计算方式是:单独x一个人计算a次,单独y一个人计算b次,然后两个加起来就计算了a+b次,两个都没有计算了一次
而这个相当于给每个计算方案分配一个权值:单独x一个人计算的是,单独y一个人计算的是,两个加起来就几乎都算了,两个都没有就是0次
于是剩下的就是
其中
你仔细品一品后面这两个东西都可以整除分块/se
于是直接上,就有优劣复杂度可以过2500ms
但为人不能这样,我们再一看,后面两个式子是一样的
而!!再一看后面那个东西本质上就是约数个数和的前缀和!!
所以说我们可以对于前预处理出来
然后后面的直接整除分块做,复杂度还是的复杂度证明和杜教筛是一样的qwq
下面代码是的,因为懒惰
code:
#include<bits/stdc++.h>
const int MAXN = 2e6 + 7;
using namespace std;
#define ll long long
const int P = 1e9 + 7;
const int inv2 = (P + 1) / 2;
int N, isp[MAXN], pri[MAXN], tot;
ll mu[MAXN];
inline void add(int &x, ll y) {
x += y;
if(x >= P)x -= P;
}
inline void add(ll &x, ll y) {
x += y;
if(x >= P)x -= P;
}
inline void init() {
N = 1e6;
mu[1] = 1;
for(int i = 2; i <= N; ++i) {
if(!isp[i]) {
pri[++tot] = i;
mu[i] = -1;
}
for(int j = 1; j <= tot && i * pri[j] <= N; ++j) {
isp[i * pri[j]] = 1;
if(i % pri[j] == 0) {
mu[i * pri[j]] = 0;
break;
}
mu[i * pri[j]] = -mu[i];
}
}
for(int i = 1; i <= N; ++i) {
mu[i] = (mu[i] * i % P + mu[i - 1] + P) % P;
// printf("%lld ?\n", mu[i]);
}
// for(int m = 1; m <= N; ++m) {
// for(int l = 1, r; l <= m; l = r + 1) {
// r = m / (m / l);
// add(f[m], 1ll * (r - l + 1) * (m / l) % P);
// }
// // if(m % 10000 == 0)printf("%d ?%lld\n", m, f[m]);
// }
return;
}
map<ll, ll> mp;
inline ll solve1(ll x) {
if(x <= N)return mu[x];
if(mp.find(x) != mp.end())return mp[x];
ll ans = 1;
for(ll l = 2, r; l <= x; l = r + 1) {//222!!!
r = x / (x / l);
ll Len = (r * (r + 1) % P * inv2 % P - l * (l - 1) % P * inv2 % P + P) % P;
add(ans, (P - solve1(x / l) * Len % P) % P);
}
mp[x] = ans;
return ans;
}
inline ll solve2(ll n) {
ll ans = 0;
for(ll l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
ll Len = (r * (r + 1) % P * inv2 % P - l * (l - 1) % P * inv2 % P + P) % P;
// printf("%d %d %lld\n", l, r, Len);
add(ans, Len * (n / l) % P);
}
// printf("%lld ?\n", ans);
return ans;
}
inline ll solve3(ll n) {
ll ans = 0;
for(ll l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
add(ans, 1ll * (n / l) * ((n / l) + 1) % P * inv2 % P * (r - l + 1) % P);
}
// printf("%lld ?!\n", ans);
return ans;
}
int n;
int main() {
scanf("%d", &n);
init();
// puts("qwq");
ll ans = 0;
// printf("%lld\n", solve1(11));
for(int l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
// printf("%d %d!!\n", l, r);
ans += (solve1(r) - solve1(l - 1)) * solve2(n / l) % P * solve3(n / l) % P;
ans %= P;
}
ans = (ans + P) % P;
printf("%lld\n", ans);
return 0;
}
P3700 [CQOI2017]小Q的表格
其实还是很妙的
b×f(a,a+b)=(a+b)×f(a,b)
盯....
相当于欧几里得算法,辗转相减
坏事了
直接带入那个gcd,d=gcd
S是约数的和
F是
gg了?带修!
修改的是每个qaq
然鹅这个查询一看就有组
一看这个m就只有1e4
所以根号平衡一下就做 完 啦 !!
P4464 [国家集训队]JZPKIL
黑题之间的经典套路(雾)
你随意反演一下,注意没有两个西格玛意味着d直接是n的约数
这里注意一下我们不能随便替换n/d和d,因为这意味着我们式子中其他地方涉及到的都要改变
注意到后面是自然幂数和,所以我们直接自闭,套用一个人的数论里面的结论有
提前
然 后 开 始 神 乎 其 技
我们注意到,后面那一坨东西:(忽略常数x,y,i)
这个东西就是直接是n的一个函数啊!设为Z
所以说就是说这个东西可以观察得到(别问我为啥可以)
!!!也就是说他是积性函数
对于积性函数,单点求值最好的解决方法就是分成所有质因数然后把质因数卷起来(或者我只会这个)
打表法
- k=0
- k=1
因为此时相当于随意分配一下
- k>=2
你观察到我们有多余两个指数分给中间那一项就可以变成0了
所以我们中间那个要么分0要么分1
对应了:
考虑卷积是啥
真不错,所以求值就是把所有这样的计算一下然后加起来即可
综上,我们还差一步求质因数,这一步可以使用MR暴力质因数分解,然后暴力算次幂qwq
自然幂数和多项式?
正当您信心满满的想去写高消的时候,发现
说明我们要伯努利数或者斯特林数了,插值就要实现多项式除法还毒瘤qaq
待填坑