HDU-5514、Frogs (容斥)

题目链接

题面:
在这里插入图片描述
题意:
有一个长度为 m m m 的环,编号为 [ 0 , m − 1 ] [0,m-1] [0,m1],有 n n n 只青蛙在 0 0 0 号点处,第 i i i 只青蛙每次可以从 k k k 号点跳到 k + a [ i ] k+a[i] k+a[i] 号点,现在这 n n n 只青蛙可以跳跃无穷次,问最后哪些点被青蛙曾经跳进去过。

题解:
很明显,第 i i i 只青蛙只会跳 g c d ( a [ i ] , m ) gcd(a[i],m) gcd(a[i],m) 那些点,我们令 a [ i ] = g c d ( a [ i ] , m ) a[i]=gcd(a[i],m) a[i]=gcd(a[i],m),现在转化为每只青蛙每次跳过 a [ i ] a[i] a[i] 个位置,其中 a [ i ] a[i] a[i] m m m 的因数,最终有多少个位置会被跳过。

我们将 a [ i ] a[i] a[i] 排序后去重,现在 a [ i ] a[i] a[i] 大约有 2 e 3 2e3 2e3 个。

其实是可以容斥做的,当时做的时候容斥写了个 2 n 2^n 2n,因为当时猜测 n n n 不会很大,然后就被卡掉了,又 Y Y YY YY 了一个欧拉函数的写法。

我老记得我在 c f cf cf 上面做过一个类似的题目,但是找了好久也没找到。

这个题 也差不多,但是没那味了。

考虑容斥。

求出 m m m 的所有约数 f a c [ i ] fac[i] fac[i],对于某一个 a [ j ] a[j] a[j] ,如果 f a c [ i ]   m o d   a [ j ] = = 0 fac[i] \ mod \ a[j]==0 fac[i] mod a[j]==0,那么记 c n t [ i ] = 1 cnt[i]=1 cnt[i]=1

我们在求 f a c [ i ] fac[i] fac[i] 在环上的贡献的时候,实际上是 f a c [ i ] fac[i] fac[i] 的倍数的 f a c [ j ] fac[j] fac[j] 位置的有 c n t [ i ] cnt[i] cnt[i] 的贡献被计算过。对于这些 c n t [ j ] cnt[j] cnt[j],我们要减去 c n t [ i ] cnt[i] cnt[i]
所以对于 a [ i ] a[i] a[i] 的倍数中的 f a c [ j ] fac[j] fac[j] 我们一开始要令 c n t [ j ] = 1 cnt[j]=1 cnt[j]=1

考虑我们在求解到 f a c [ i ] fac[i] fac[i] 的时候,数组 c n t [ i ] cnt[i] cnt[i] 为因子 f a c [ i ] fac[i] fac[i] 为当前应该被计算的贡献。我们计算过 c n t [ i ] cnt[i] cnt[i] 后,那么 f a c [ i ] fac[i] fac[i] 的倍数均有 c n t [ i ] cnt[i] cnt[i] 的贡献被计算过,不应再计算,所以应该在 f a c [ i ] fac[i] fac[i] 的倍数处,减去 c n t [ i ] cnt[i] cnt[i] 的贡献。

时间复杂度 O ( s u m ( f a c ) 2 ) O(sum(fac)^2) O(sum(fac)2)

代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#include<queue>
#include<bitset>
#include<map>
#include<unordered_map>
#include<unordered_set>
#include<set>
#include<ctime>
#define ui unsigned int
#define ll long long
#define llu unsigned ll
#define ld long double
#define pr make_pair
#define pb push_back
#define lc (cnt<<1)
#define rc (cnt<<1|1)
#define len(x)  (t[(x)].r-t[(x)].l+1)
#define tmid ((l+r)>>1)
#define fhead(x) for(int i=head[(x)];i;i=nt[i])
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)>(y)?(y):(x))
using namespace std;

const int inf=0x3f3f3f3f;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const double dnf=1e18;
const double alpha=0.75;
const int mod=998244353;
const double eps=1e-8;
const double pi=acos(-1.0);
const int hp=13331;
const int maxn=100100;
const int maxm=100100;
const int maxp=100100;
const int up=1100;

ll fac[maxn],cnt[maxn],tot=0;
ll a[maxn];

ll gcd(ll a,ll b)
{
    if(b==0) return a;
    return gcd(b,a%b);
}

void gao(ll n)
{
    tot=0;
    for(ll i=1;i*i<=n;i++)
    {
        if(n%i) continue;
        fac[++tot]=i;
        if(i*i!=n) fac[++tot]=n/i;
    }
}

int main(void)
{
    int tt;
    scanf("%d",&tt);
    int pp=0;
    while(tt--)
    {
        ll n,m;
        scanf("%lld%lld",&n,&m);
        gao(m);
        for(int i=1;i<=tot;i++)
            cnt[i]=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&a[i]);
            a[i]=gcd(a[i],m);
        }
        sort(fac+1,fac+tot+1);
        sort(a+1,a+n+1);
        int cm=unique(a+1,a+n+1)-(a+1);
        for(int i=1;i<=cm;i++)
        {
            for(int j=1;j<=tot;j++)
                if(fac[j]%a[i]==0) cnt[j]=1;
        }

        ll ans=0;
        for(int i=1;i<=tot;i++)
        {
            ans+=(m/fac[i]-1)*fac[i]*(m/fac[i])/2*cnt[i];
            for(int j=i+1;j<=tot;j++)
            {
                if(fac[j]%fac[i]==0) cnt[j]-=cnt[i];
            }
        }
        printf("Case #%d: %lld\n",++pp,ans);
    }
    return 0;
}


再记录一下欧拉函数的解法,我感觉数据拉满可能会T。

如同上一种解法,我们求解出 m m m 的因子中所有的 a [ i ] a[i] a[i],和 a [ i ] a[i] a[i] 的倍数,记为 f a c [ i ] fac[i] fac[i]

显然,如果 a [ i ] a[i] a[i] 的某些倍数在 m m m 的环上标记了,那么 a [ i ] a[i] a[i] 的倍数的倍数肯定在 m m m 的环上标记了。

考虑到对于所有的 f a c [ i ] fac[i] fac[i] m   m o d   f a c [ i ] = 0 m\ mod \ fac[i]=0 m mod fac[i]=0,即 g c d ( f a c [ i ] , m ) = f a c [ i ] gcd(fac[i],m)=fac[i] gcd(fac[i],m)=fac[i],我们对于每一个 f a c [ i ] fac[i] fac[i] 计算 g c d ( k ∗ f a c [ i ] , m ) = f a c [ i ] gcd(k*fac[i],m)=fac[i] gcd(kfac[i],m)=fac[i] k ∗ f a c [ i ] k*fac[i] kfac[i] 的累加和。因为这样的 k ∗ f a c [ i ] k*fac[i] kfac[i] 一定不会被重复计算。

a n s = ∑ i = 1 t o t ∑ k = 1 m / f a c [ i ] [ g c d ( k ∗ f a c [ i ] , m ) = f a c [ i ] ] ∗ k ∗ f a c [ i ] ans=\sum\limits_{i=1}^{tot}\sum\limits_{k=1}^{m/fac[i]}[gcd(k*fac[i],m)=fac[i]]*k*fac[i] ans=i=1totk=1m/fac[i][gcd(kfac[i],m)=fac[i]]kfac[i]

考虑内层循环:

∑ k = 1 m / f a c [ i ] [ g c d ( k ∗ f a c [ i ] , m ) = f a c [ i ] ] ∗ k ∗ f a c [ i ] \sum\limits_{k=1}^{m/fac[i]}[gcd(k*fac[i],m)=fac[i]]*k*fac[i] k=1m/fac[i][gcd(kfac[i],m)=fac[i]]kfac[i]

∑ k = 1 m / f a c [ i ] [ g c d ( k , m / f a c [ i ] ) = 1 ] ∗ k ∗ f a c [ i ] \sum\limits_{k=1}^{m/fac[i]}[gcd(k,m/fac[i])=1]*k*fac[i] k=1m/fac[i][gcd(k,m/fac[i])=1]kfac[i]

其中 ∑ k = 1 m / f a c [ i ] [ g c d ( k , m / f a c [ i ] ) = 1 ] ∗ k = φ ( m / f a c [ i ] ) ∗ ( m / f a c [ i ] ) 2 \sum\limits_{k=1}^{m/fac[i]}[gcd(k,m/fac[i])=1]*k=\dfrac{φ(m/fac[i])*(m/fac[i])}{2} k=1m/fac[i][gcd(k,m/fac[i])=1]k=2φ(m/fac[i])(m/fac[i])

那么上式中第二个 ∑ \sum 就等于 φ ( m / f a c [ i ] ) ∗ m 2 \dfrac{φ(m/fac[i])*m}{2} 2φ(m/fac[i])m

即我们对于每一个 f a c [ i ] fac[i] fac[i] 计算上式即可。

注意,对于有 f a c [ i ] = 1 fac[i]=1 fac[i]=1 的情况,我们直接特判掉。

但是一定会有 f a c [ i ] = m fac[i]=m fac[i]=m 的情况,此时,其对于答案是没有贡献的,因为没有 m m m 号节点。
我们只需要在最终的答案中减去 m / 2 m/2 m/2即可。

代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#include<queue>
#include<bitset>
#include<map>
#include<unordered_map>
#include<unordered_set>
#include<set>
#include<ctime>
#define ui unsigned int
#define ll long long
#define llu unsigned ll
#define ld long double
#define pr make_pair
#define pb push_back
#define lc (cnt<<1)
#define rc (cnt<<1|1)
#define len(x)  (t[(x)].r-t[(x)].l+1)
#define tmid ((l+r)>>1)
#define fhead(x) for(int i=head[(x)];i;i=nt[i])
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)>(y)?(y):(x))
using namespace std;

const int inf=0x3f3f3f3f;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const double dnf=1e18;
const double alpha=0.75;
const int mod=998244353;
const double eps=1e-8;
const double pi=acos(-1.0);
const int hp=13331;
const int maxn=100100;
const int maxm=100100;
const int maxp=100100;
const int up=1100;

ll a[maxn];
int n,m,cnt;
ll ans=0;

ll gcd(ll a,ll b)
{
    if(b==0) return a;
    return gcd(b,a%b);
}

ll lcm(ll a,ll b)
{
    return a/gcd(a,b)*b;
}

ll P(ll n)
{
    ll k=n;
    for(ll i=2;i*i<=n;i++)
    {

        if(n%i==0)
        {
            k=k-k/i;
            while(!(n%i)) n/=i;
        }
    }
    if(n>1) k=k-k/n;
    return k;
}

ll fac[maxn];
bool ha[maxn];
void gao(ll n)
{
    cnt=0;
    for(int i=1;i*i<=n;i++)
    {
        if(n%i) continue;
        fac[++cnt]=i;
        if(i*i!=n) fac[++cnt]=n/i;
    }
}

int main(void)
{
    int tt;
    scanf("%d",&tt);
    int pp=0;
    while(tt--)
    {
        ans=0;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&a[i]);
            a[i]=gcd(a[i],m);
        }
        gao(m);
        sort(a+1,a+n+1);
        sort(fac+1,fac+cnt+1);
        int up=unique(a+1,a+n+1)-(a+1);

        for(int i=1;i<=cnt;i++)
            ha[i]=false;

        for(int i=1;i<=up;i++)
        {
            for(int j=1;j<=cnt;j++)
            {
                if(fac[j]%a[i]==0) ha[j]=true;
            }
        }


        if(a[1]==1)
        {
            printf("Case #%d: %lld\n",++pp,1ll*m*(m-1)/2);
            continue;
        }

        for(int i=1;i<=cnt;i++)
        {
            if(!ha[i]) continue;
            ans=ans+m*P(m/fac[i])/2;
        }
        printf("Case #%d: %lld\n",++pp,ans-m/2);
    }
    return 0;
}


©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页