集合
题目链接:NOI2022省选挑战赛 Contest3 B
题目大意
你有一个多重集合,一开始有一个数,然后你可以做无限次两种操作之一:
选两个正整数 a,b,删掉它们并加入 a^b。
选一个正整数可以表示为 a^b 的形式(其中 a,b 不小于 2),删去它并加入 a,b。
然后问你 [1,n] 中有多少个数作为一开始集合中的数能使得可以通过变换得到任意数。
思路
一道非常神奇非常神仙的构造。
那这种从一个变到全部的我们不难想象到两种方法:
从
i
i
i 到
i
+
1
i+1
i+1,或者你可以无限放大一个数,然后从
i
i
i 到
i
−
1
i-1
i−1。
但是它这个是次方,不太好搞,考虑这个性质:
a
b
c
=
a
b
c
a^{b^c}=a^{bc}
abc=abc。
那如果我们有两个数
a
,
b
c
a,bc
a,bc,那我们可以这样:
a
b
c
=
a
b
c
→
a
,
b
c
→
a
,
b
,
c
a^{bc}=a^{b^c}\rightarrow a,b^c\rightarrow a,b,c
abc=abc→a,bc→a,b,c。
那这个就是当你有一个数的时候,你可以利用另外一个数把它分解因数。
那自然它也是可逆的,即当集合中有三个数的时候,我们可以把任意两个数乘起来。
那这样次方就变成了乘法,看起来就可做多了。
然后我们会发现我们可以利用上面的性质无限放大一个数:
当我们有一个合数
a
b
(
a
⩾
3
)
ab(a\geqslant 3)
ab(a⩾3) 以及另外一个数的时候,我们就可以无限复制
b
b
b,然后无限个
b
b
b 合并在一起就是无限大。
怎么无限复制呢,看(题解)操作:
a
b
→
a
,
b
→
b
a
→
a
ab\rightarrow a,b\rightarrow b^a\rightarrow a
ab→a,b→ba→a 个
b
→
a
−
1
b\rightarrow a-1
b→a−1 个
b
,
a
b
b,ab
b,ab,然后
a
b
ab
ab 再弄,就无限复制了。
然后如果有一个
b
,
k
b,k
b,k,那我们可以这样得到
k
−
1
k-1
k−1:
b
,
k
→
b
k
=
b
∗
b
k
−
1
→
b
,
b
,
k
−
1
b,k\rightarrow b^k=b*b^{k-1}\rightarrow b,b,k-1
b,k→bk=b∗bk−1→b,b,k−1。
所以如果一个数是合法的,那它可以表示成
a
b
a^b
ab,其中
a
,
b
a,b
a,b 有一个是合数
a
b
(
a
⩾
3
)
ab(a\geqslant 3)
ab(a⩾3)。
那这个条件太复杂,我们可以考虑变成
⩾
6
\geqslant 6
⩾6 的合数。
然后你直接搞完就会发现它挂了。
有一个特别的东西就是
4
4
4。
因为你搞搞会发现
2
4
2^4
24 是不行的,但是别的时候有个
4
4
4 是没问题的(
a
4
=
(
a
2
)
2
a^4=(a^2)^2
a4=(a2)2)。
所以我们就不要求
⩾
6
\geqslant 6
⩾6,然后最后减去多算的
16
16
16 即可。
代码
#include<cstdio>
#define ll unsigned long long
using namespace std;
ll n, ans;
ll prime[10000001];
ll np[10000001];
bool see[10000001];
void Init() {
for (ll i = 2; i <= 10000000; i++) {
if (!np[i]) prime[++prime[0]] = i, np[i] = i;
for (ll j = 1; j <= prime[0] && i * prime[j] <= 10000000; j++) {
np[i * prime[j]] = prime[j]; if (i % prime[j] == 0) break;
}
}
}
int main() {
Init();
scanf("%llu", &n);
for (ll i = 2; i * i <= n; i++) {
if (see[i]) continue;
for (ll j = 2, now = i * i; now <= n; now *= i, j++) {
if (now <= n / now) see[now] = 1;
if (np[i] != i || np[j] != j) ans += now;
if (now > n / i) break;
}
}
printf("%llu", ans - (ans >= 16) * 16);
return 0;
}