解题报告 之 SOJ3191 Free square
Description
Time Limit: 5000 MS Memory Limit: 65536 KDescription
A positive integer is said to be squarefree if it is divisible by no perfect square larger than 1. For example, the first few squarefree numbers are {1, 2, 3, 5, 6, 7, 10, 11, 13, 14, 15, 17, 19, ...}. Can you tell me the K-th ( 1-indexed ) smallest squarefree number.Input
The first line of the input will be a integer to represent the number of test cases. For each test case there is only one line contains only one integer K. ( 1 <= K <= 10^9 ) There is a blank line before each test case.Output
For each test case output the answer on a single line: The K-th smallest squarefree number.Sample Input
6 1 2 7 1000 1234567 1000000000Sample Output
1 2 10 1637 2030745 1644934081Source
题目大意:非平方构成数定义为一个数不能被任何>1的完全平方数整除,则它为非平方构成数,现在问你第k个非平方构成数是什么?注意k最大为10^9。
分析:这么大的数据,肯定不能一个一个试吧,那么怎么办呢?其实这题是容斥原理,还记得筛选区间内是任意一个素数的倍数的题么?这道题思路异曲同工:先假设区间内所有数都满足条件,然后减去那些是某数的平方倍数的数,再加上同时是某两个平方数倍数的数,再减去同时是三个平方数倍数的数,……。但请注意,这里用于筛选的平方数的选取条件为每个质数作为因子最多出现一次。因为如果某个质因子出现了多次,那么它会造成不必要的重新筛,增加容斥的步骤。
那么我们先取得250内的素数,通过250内的素数去判断500000内的数是应该加还是减还是不关心(取决与其质因子数量)。在1~50000中,如果一个数有重复的质因子,我们则不用它,系数数组co[i]=0;如果一个数有着不重复的奇数个质因子,那么说明容斥时应该减(因为它是平方数),co[i]=-1;如果一个数有着不重复的偶数个质因子,说明容斥时应该加(因为多减了),co[i]=1。
构造好了系数矩阵之后我们就开始容斥以确定某个数之前有多少个非平方构成数。这里因为问的是第几个数是多少,巧妙的使用了二分,如果假设这个数是mid,如果可以满足那么mid容斥完后应该>=k。
最后输出答案的意义即为,<=ans 的数中有k个非平方构成数,那么ans就是第k个非平方构成数。
上代码:
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 4000;
const int MAXM = 100100;
int isprime[MAXN];
int prime[MAXN];
int co[MAXM];
int cnt;
void getP() //线性素数筛
{
for(int i = 1; i < MAXN; i++)
isprime[i] = 1;
for(int i = 2; i < MAXN; i++)
{
if(isprime[i])
prime[cnt++] = i;
for(int j = 0; j < cnt&&i*prime[j] < MAXN; j++)
{
isprime[i*prime[j]] = 0;
if(i%prime[j] == 0)
break;
}
}
}
void getCo() //计算素数矩阵
{
for(int i = 2; i < MAXM; i++)
{
co[i] = 1;
int tem = i;
for(int j = 0; prime[j] * prime[j] <= tem; j++)
{
if(tem%prime[j] == 0)
{
co[i] = -co[i]; //找到一个质因子则改变容斥时的加减性
tem /= prime[j];
}
if(tem%prime[j] == 0)
{
co[i] = 0; //不在我们的考虑范围内
break;
}
}
if(tem > 0&&co[i]!=0) co[i] = -co[i];
}
}
int main()
{
int kase, n;
cin >> kase;
getP();
getCo();
while(kase--)
{
cin >> n;
ll l = 1, r = 2e10;
ll mid, tem,ans;
while(l <= r) //二分数,找到第k个数
{
mid = l + (r - l) / 2;
tem = mid;
for(long long i = 2; i*i <= mid; i++)
{
tem = tem + (mid / (i*i))*co[i]; //mid/(i*i)算出在mid范围内有多少个数能被(i*i)整除,再乘上容斥系数
}
if(tem >= n)
{
r = mid - 1;
ans = mid;
}
else l = mid + 1;
}
cout << ans << endl;
}
return 0;
}
容斥原理果然好难的样子,继续修炼。。