洛谷P4318 和 计蒜客 A1991 convex hull (模比乌斯与容斥原理)

在这里插入图片描述
这个题,可以设Ai表示含有pi的平方的数的集合,
那么所有的不为素数平方的数的集合个数就是Ai取反后的交集所形成的集合的元素个数。
根据这个,可以推导。p1=2,那么A1的个数就是n/(22) ,p2=3,A2的个数就是n/(33),A1与A2的交集的个数则为n/(2*3)^2 ,根据容斥原理的公式,A1,A2应该被减去,A1与A2的交集应该被加上,那么容斥系数等于莫比乌斯函数值。又由于是找的平方,那么一个数n的素因子的平方不大于n,也就是说该因子不大于根号下n,那么枚举素因子只要找到根号下n即可,找多了没有意义。然后就是根据k的值去确定这个n的值应该有多大。10^9个这样的数,则需要大约三十个素数的相互组合,也就是C(30,i) (i从0到31)的和为2^30次方,而由前三十个素数相乘的结果为7581744426003940878,但是实际上没有这么大,因为上面的情况并没有考虑素数的个数,如果把素数的个数算上的话, 2e10/ln(2e10) 约为8e8, 且2e5以内的素数个数约为16000,而C(16000,2)约为2e8 ,其实用不了这么大。那么上限可以取 1e10。
然后二分枚举这个要找的数据,判断这个数据之前有多少个符合条件数来划分区间。

#include <bits/stdc++.h>
#define sd(a) scanf("%d", &(a))
#define sdd(a) scanf("%lld", &(a))
#define pd(a) printf("%d\n", a)
#define pdd(a) printf("%lld\n", a)
#define int long long
#define ll long long
#define iint __int128
using namespace std;
template <class cl>
void read(cl &x)
{
    x = 0;
    int f = 0;
    char ch;
    ch = getchar();
    while (!isdigit(ch))
    {
        f = f | (ch == '-'), ch = getchar();
    }
    while (isdigit(ch))
    {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    x = f ? -x : x;
    return;
}
template <class cl>
void put(cl x)
{
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        put(x / 10);
    putchar(x % 10 + '0');
    return;
}
const int N = 1e5 + 10;
ll fmul(ll a, ll b, ll p)
{
    return (a * b - (ll)((long double)a * b / p) * p + p) % p;
}
int g[N+10];
int cnt = 0;
int prime[N+10];
int mu[N+10];
void getmu()
{
    mu[1] = 1;
    for (int i = 2; i <= N; i++)
    {
        if (g[i] == 0)
        {
            prime[++cnt] = i;
            mu[i] = -1;
        }
        for (int j = 1; j <= cnt && i <=N / prime[j]; j++)
        {
            g[i * prime[j]] = 1;
            if (i % prime[j] == 0)
            {
                break;
            }
            mu[i * prime[j]] = -mu[i];
        }
    }
}
int k;
int jud(int mid)
{
    int res=0;
   for(int i=1;i<=mid/i;i++)
   {
       if(mu[i]==0) continue;
      res=res+mu[i]*(mid/(i*i));
   }
   return res;
}
signed main()
{
  getmu();
 // cout<<mu[6]<<endl;
  int t;
  read(t);
  //int k;
  while(t--)
  {
      read(k);
      int l=k;
      int r=k*2;
      int mid;
      int ans;
      while(l<r)
      {
        mid=(l+r)/2;
         // mid与(mid+mid+1)/2会相差一,mid符合条件但是mid+1 不符合条件,所以mid是要找的那个数字。
       if(jud(mid)>=k)
       {
           r=mid;
       }
       else 
       {
           l=mid+1;
       }
      }
      put(r);
      puts("");
  }
  return 0;
}

在这里插入图片描述
这个东西如果数据量为1e6的话,那么可以直接线性筛把这个函数的表求出来,然后再两次前缀和,但是,数据量大的吓人,这样肯定是不行,直接硬来不行,考虑用数学简化。
然后可以发现这道题和上一道题的套路差不多,但是这道题目中的集合不是集合中元素的个数,而是求集合中元素的平方和和立方和,需要用到平方和与立方和公式,容斥的方法和上题一样。然后还有一个小问题是数据过大,可以用__int128,或者是a/b%p=a%(b*p)/p+快速乘。

#include <bits/stdc++.h>
#define int long long
#define ll long long
using namespace std;
template <class cl>
void read(cl &x)
{
    x = 0;
    int f = 0;
    char ch;
    ch = getchar();
    while (!isdigit(ch))
    {
        f = f | (ch == '-'), ch = getchar();
    }
    while (isdigit(ch))
    {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    x = f ? -x : x;
    return;
}
template <class cl>
void put(cl x)
{
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        put(x / 10);
    putchar(x % 10 + '0');
    return;
}
const int N = 1e5 + 10;
ll fmul(ll a, ll b, ll p)
{
    return (a * b - (ll)((long double)a * b / p) * p + p) % p;
}
int g[N+10];
int cnt = 0;
int prime[N+10];
int mu[N+10];
void getmu()
{
    mu[1] = 1;
    for (int i = 2; i <= N; i++)
    {
        if (g[i] == 0)
        {
            prime[++cnt] = i;
            mu[i] = -1;
        }
        for (int j = 1; j <= cnt && i <=N / prime[j]; j++)
        {
            g[i * prime[j]] = 1;
            if (i % prime[j] == 0)
            {
                break;
            }
            mu[i * prime[j]] = -mu[i];
        }
    }
}
signed main()
{
    //cout<<fmul(1e10,1e10,6);
    //cout<<fmul(1000,1000,7);
    getmu();
    //cout<<mu[5]<<endl;
    //ios::sync_with_stdio(0);
    //cin.tie(0);
   // cout.tie(0);
    int n, p;
    // read(n);
    // read(p);
    while (~scanf("%lld%lld",&n,&p))
    {
        int res1 = 0;
        int tt;
        for (int i = 1; i * i <= n; i++)
        {
            tt = 0;
            int zz=n/(i*i);
            tt=fmul(fmul(zz,zz+1,6*p),2*zz+1,6*p);
            tt=tt/6;
            tt = tt * mu[i];
            tt = fmul(tt, i * i, p);
            tt = fmul(tt, i * i, p);
            res1 = (res1 + tt) % p;
        }
        res1 = fmul(n + 1, res1, p);
        int res2 = 0;
        for (int i = 1; i * i <= n; i++)
        {
            tt = 0;
            int zz=n/(i*i);
            tt=fmul(fmul(zz,zz+1,4*p),fmul(zz,zz+1,4*p),4*p);
            tt=tt/4;
            tt = tt * mu[i];
            tt = fmul(tt, i * i * i, p);
            tt = fmul(tt, i * i * i, p);
            res2 = (res2 + tt) % p;
        }
        int ans = 0;
        ans = (res1 - res2 + p) % p;
        printf("%lld\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值