Hdu 5514 类莫比乌斯函数 容斥原理

这个做法和其他题解的做法是等价的。
但是那些题解没有给出严格证明,强迫症患者表示非常难受。
强行证明一波。过程中发现和莫比乌斯的联系。

题意:有m个石子围成一圈, 有n只青蛙从跳石子, 都从0号石子开始, 每次越过a[i]个石子
问所有被至少踩过的石子的序号之和

思路:
设所有踩到的石头下标集合为 P,题目求sum(P),青蛙集合为a
总共m个石子,青蛙每次跳ai。等价于m个石子,青蛙每次跳gcd(m,ai)。并且 当 gcd(m,ai)|k 时,第k个石头可以背踩到。
先对青蛙集合a做一个等价替代,ai = gcd(m,ai) (0 <=i< n),于是 P = {a集合中任意一个的倍数i | i < m} (i不能重复),求sum(p)。 变成了一个关于倍数的问题,直观感觉和莫比乌斯函数有联系。

另sta为一个01序列,表示一个a的子集,第i位1表示ai属于这个子集。
容易想到的容斥方法:

ans=sta=0top(1)|sta|V(lcm(sta))

lcm(sta) 表示子集中所有数的最小公倍数。V(x)为 sum{x的倍数i | i < m}。
当集合较小时可以用这个公式算,复杂度是 o(2|a|)

到这里有一个性质还没用到,
a中元素是计算gcd(m,ai)得到的, 所以a中元素都是m的约数。所以lcm(sta) 也一定是m的约数。
m的约数个数比较少,考虑把容斥公式从枚举子集转化为 枚举 lcm(sta)的值得:

ans=diV(di)F(di)

d为m的所有约数(就是lcm的值)。 F(n)是什么?
F(n) 恰好是和 莫比乌斯函数 拥有类似性质。
个人感觉 莫比乌斯函数 是在质数集合 里的容斥,而F(d)是在一个给定集合内的容斥。
莫比乌斯函数的性质:
d|nμ(d)==(n==1)

F(d)函数的性质:
d|nF(d)==g(d)

g(d)表示d的倍数是否要被计入答案,若d的倍数都记入答案则g(d)=1

于是这个题目只要处理m的所有约数放入数组D,根据F函数的性质计算F,然后枚举 lcm 进行容斥(就是枚举D数组的元素,容斥就是套上面容斥公式)。

#include
#include
#include
using namespace std;
#define MS(x,y) memset(x,y,sizeof(x))
typedef long long LL;
const int N = 10010;
int casei;
int d[N],m,n,cnt;
int F[N];
void init()
{
    for(int i=0;iint x;
        scanf("%d",&x);
        x = __gcd(x,m);
        for(int j=0;jif(d[j]%x==0) F[j]=1;
    }
    F[cnt-1]=0;
    for(int i = 0; i < cnt; ++i)
        for(int j = 0; j < i; ++j)
            if(d[i]%d[j] == 0) F[i] -= F[j];
}
inline LL V(int x)
{
    LL t = (m-1)/x;
    return (1+t)*t/2*x;
}
void solve()
{
    MS(F,0); cnt = 0;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=sqrt(m);i++)
    {
        if(m%i==0)
        {
            d[cnt++]=i;
            if(i*i!=m) d[cnt++]=m/i;
        }
    }
    sort(d,d+cnt);
    init(); LL ans = 0;
    for(int i = 0; i < cnt; i++)if(F[i]) ans += V(d[i]) * F[i];
    printf("Case #%d: %lld\n",++casei,ans);

}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--) solve();
    return 0;
}

你可能感兴趣的:(ACM算法竞赛)