题解:AT_agc003_d [AGC003D] Anticube
前置知识:埃氏筛法(或线性筛法)、质因数分解。
再次感谢 @Tenshi 的题解,提供了重要思路。
本题解在更加详细的同时,增加了一些结论证明,简化了找质因数的过程,不需要再使用 Pollard-Rho 这种高级算法。通过这种方法,这道题的难度可以降低到蓝或更低。
题目要求“任意两个数的积都不是完全立方数”,即数与数之间存在互斥关系。又要求“选出最多的数”,很容易想到最大独立集。考虑如何快速判断两个数是否互斥:
将一个数质因数分解后,是否是立方数只与其是否所有质因子的指数都是
如此操作以后,一个数可以在所有质因子指数都模
这样,某个数的简化形式的互斥对象就有了唯一确定的简化形式,并且这是一一对应的,
根据这个计算方式建图,以所有的简化形式为结点,所有的互斥关系为边,所求即为这张图上的最大独立集。
这样建图出来以后可以发现,所有的完全立方数简化形式都为
此外,非完全立方数的互斥对象都一定不是自己,证明:对非完全立方数进行简化,其至少有一个质因子的指数不为
综上所述,除去完全立方数是可能有自环的孤点,其它非完全立方数点总是两两一对连边,且不会形成自环,在这样的图上找最大独立集就简单多了,两侧的点只能选一边,当然是选大的那一侧了。
具体来说,设简化形式
接下来考虑如何求取某个数的简化形式和其互斥对象的简化形式:
简化某个数需要将其质因数分解,暴力当然是不行的,我们需要一些优化。
-
首先利用埃筛/线筛找出
\sqrt{10^{10}}=10^5 内的所有质数,数量级为\frac{10^5}{\ln 10^5} \approx 10^4 个。 -
然后对于每一个非完全立方数
a_i ,遍历质数集合将其质因数分解,记录每一个质因子的出现次数c ,将p^{c \bmod 3} 计入简化形式,p^{(3- c \bmod 3) \bmod 3} 计入互斥对象简化形式。 -
分解完毕后即获得这个数和其互斥对象的简化形式,分解时还可判断
p_i^2 \le x 来加速。 -
记录互斥关系,累加该简化形式的出现次数。
-
统计答案,对于每一对互斥关系,取两边较大的出现次数(注意互斥关系需要判重);如果输入有完全立方数则需额外加一。
记
AC 记录:洛谷,AtCoder。
代码:
#include<cstdio>
#include<bitset>
#include<vector>
#include<unordered_map>
#define LL long long
using namespace std;
const int N=1e5+5,SA=1e5;
int n;
LL a[N];
bitset<SA+5> is_prime;
int prime[N],prime_idx;
void Erato()
{
for(int i=2;i<=SA;i++)
is_prime[i]=true;
for(int i=2;i<=SA;i++)
if(is_prime[i])
{
prime[++prime_idx]=i;
for(LL j=1ll*i*i;j<=SA;j+=i)
is_prime[j]=false;
}
return;
}
unordered_map<LL,int> ump;
unordered_map<LL,LL> rev;
void connect(int id)
{
LL x=a[id];
auto spow = [&](LL x,int y) -> LL //计算x的y次方(y≤3)
{
switch(y)
{
case 0: return 1;
case 1: return x;
case 2: return x*x;
case 3: return 1;
default: return -1;
}
};
LL simp=1,cur=1;
for(int i=1;i<=prime_idx&&prime[i]*prime[i]<=x;i++)
if(x%prime[i]==0)
{
int cnt=0;
while(x%prime[i]==0)
{
x/=prime[i];
cnt++;
}
cnt%=3;
simp*=spow(prime[i],cnt);
cur*=spow(prime[i],3-cnt);
}
if(x>1)
{
simp*=spow(x,1);
cur*=spow(x,2);
}
ump[simp]++;
rev[simp]=cur;
rev[cur]=simp;
ump[cur];
return;
}
unordered_map<LL,bool> is_cub;
unordered_map<LL,bool> vst;
int main()
{
Erato();
for(int i=1;i<=2200;i++)
is_cub[1ll*i*i*i]=true;
scanf("%d",&n);
bool have_cube=false;
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
if(is_cub[a[i]]) have_cube=true;
else connect(i);
}
int ans=0;
for(pair<LL,int> p: ump)
{
if(vst[p.first]) continue;
ans+=max(p.second,ump[rev[p.first]]);
vst[rev[p.first]]=true;
}
printf("%d\n",ans+have_cube);
return 0;
}