杭电多校第十场1004(容斥)
2021杭电多校第十场1004(容斥)
Problem - 7080 (dingbacode.com)
题意:
给你两个数n,k统计不超过n的不被前k个质数整除的数字个数
\(1\le n\le 10^{18},1\le k\le16\)
t组询问\(1\le t\le 10^5\)
思路:
考虑最经典的容斥
枚举每个数选还是不选,那么ans+=\((\frac{n}{S})\times(-1)^{the\ number\ of\ selected\ number}\)
即不断地加上选奇数个,减去选偶数个
复杂度为\(O(2^k)\)
由于t比较大,这样显然过不了
考虑当k=8时,其乘积S= 2 ∗ 3 ∗ 5 ∗ 7 ∗ 11 ∗ 13 ∗ 17 ∗ 19 = 9699690
那么\(ans(n)=ans(9699690)\times\lfloor\frac{n}{9699690}\rfloor+ans(n\ mod\ 9699690)\)
预处理1~9699690即可O(1)查询\(n-\frac{n}{1个}+\frac{n}{2个}\cdots-\frac{n}{7个}+\frac{n}{8个}\)
对于k>8的情况,可以\(O(2^{k-8})\)算出第9到第k个质数的情况,考虑前后相交的情况
显然有\(\lfloor\frac{n}{i\times j}\rfloor=\lfloor\frac{\lfloor\frac{n}{i}\rfloor}{j}\rfloor\)
假设我们枚举后面一段的乘积为res
与前面关联后我们需要求\(-\frac{n}{1个*res}+\frac{n}{2个*res}\cdots-\frac{n}{7个*res}+\frac{n}{8个*res}\),再根据组合后的奇偶判断符号
将res放到分子上,已知我们可以O(1)去求ans(\(\frac{n}{res}\))=\(\frac{n}{res}-\frac{n}{1个*res}+\frac{n}{2个*res}\cdots-\frac{n}{7个*res}+\frac{n}{8个*res}\)
即可以在枚举后半部分时,O(1)处理前后关系
#include<bits/stdc++.h>
using namespace std;
const int maxn=9699690;
int a[1<<17|1],ans1=0,prime[100],bj[110],cnt;
long long c[2*maxn+50];
struct tnode{
int l,r;
}e[1<<17|1];
bool cmp(tnode l,tnode r)
{
return l.l<r.l;
}
long long b[1<<17|1];
void get_prime(int x)
{
for(int i=2;i<=x;i++)
{
if(!bj[i])prime[++cnt]=i;
for(int j=1;j<=cnt&&prime[j]*i<=x;j++)
{
bj[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
}
int cal(int x)
{
int ans=0;
while(x)
{
ans++;
x-=(x)&(-x);
}
return ans;
}
long long get_sum(long long n)
{
return c[9699690]*(n/9699690)+c[n%9699690];
}
int main()
{
get_prime(100);
int t;
scanf("%d",&t);
for(int i=1;i<=8;i++)
{
a[1<<(i-1)]=prime[i];
}
for(int i=1;i<(1<<8);i++)
{
for(int j=0;j<8;j++)
{
if((i&(1<<j))==0)
{
a[i|(1<<j)]=a[i]*prime[j+1];
}
}
}
for(int i=1;i<=maxn;i++)
c[i]=1;
for(int i=1;i<(1<<8);i++)
{
if(cal(i)&1)
{
for(int j=1;a[i]*j<=maxn;j++)
{
c[a[i]*j]-=j;
c[a[i]*(j+1)]+=j;
}
}
else
{
for(int j=1;a[i]*j<=maxn;j++)
{
c[a[i]*j]+=j;
c[a[i]*(j+1)]-=j;
}
}
}
for(int i=1;i<=maxn;i++)
c[i]+=c[i-1];
for(int i=1;i<=8;i++)
{
b[1<<(i-1)]=prime[i+8];
}
for(int i=1;i<(1<<8);i++)
{
for(int j=0;j<8;j++)
{
if((i&(1<<j))==0)
{
b[i|(1<<j)]=b[i]*prime[j+9];
}
}
}
while(t--)
{
int k;long long n;
scanf("%lld%d",&n,&k);
if(k<=8)
{
long long ans=n;
for(int i=1;i<(1<<k);i++)
{
if(cal(i)&1)ans-=n/a[i];
else ans+=n/a[i];
}
printf("%lld\n",ans);
}
else
{
k-=8;
long long ans=n;
ans=ans+(get_sum(n)-n);
for(int i=1;i<(1<<k);i++)
{
if(cal(i)&1)
{
ans-=n/b[i];
ans-=(get_sum(n/b[i])-n/b[i]);
}
else
{
ans+=n/b[i];
ans+=(get_sum(n/b[i])-n/b[i]);
}
}
printf("%lld\n",ans);
}
}
return 0;
}