hdu5514容斥原理和群论

题目描述

There are m stones lying on a circle, and n frogs are jumping over them.
The stones are numbered from 0 to m−1 and the frogs are numbered from 1 to n. The i-th frog can jump over exactly ai stones in a single step, which means from stone j mod m to stone (j+ai) mod m (since all stones lie on a circle).

All frogs start their jump at stone 0, then each of them can jump as many steps as he wants. A frog will occupy a stone when he reach it, and he will keep jumping to occupy as much stones as possible. A stone is still considered “occupied” after a frog jumped away.
They would like to know which stones can be occupied by at least one of them. Since there may be too many stones, the frogs only want to know the sum of those stones’ identifiers.

算法思路

  1. 题目的意思是这样的,有m个石子围成了一圈,从0-m-1编号,每一只青蛙可以每一次跳a[i]格,要我们求出所有会被青蛙到达的石块的序号的和。
  2. 一开始的时候,一个十分朴素的想法是,如果青蛙每一次跳跃a[i]格,那么只要是gcd(a[i],m)的倍数都可以被这只青蛙到达。这些数构成了一个等差数列,我们只是需要O(1)的时间就可以求出其和,但是这样的话我们会多算很多(重复计算),我们需要规避这些事情。
  3. 一个更好的解法是,我们打表打出所有的m的因数,很显然,gcd(a[i],m)也是m的一个因数,所以其必须在我们所打的这个表当中。这样做的一个好处是,规模大大减小了,因为实际上一个数的因数的数量的数量级是O(lgn),远小于n。
  4. 但是,会存在一个问题,就是有一些序号被多次计算了,比如6的倍数一定是3的倍数,所以num[]数组记录其已被计算的次数,vis[]数组记录其需要被记录的次数,然后就是等差数列求和了。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;

#define MAXN 10005
#define LL long long
#define MOD 1000000007

vector<LL>clr;
LL Arr[MAXN];
int n,t;
LL m;
int cas=0;
LL vis[MAXN],num[MAXN];

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

int main()
{
    //freopen("input","r",stdin);
    int i,j;
    LL ans;
    scanf("%d",&t);

    while(t--){
        ans = 0;
        scanf("%d%lld",&n,&m);

        memset(num,0,sizeof(num));
        memset(vis,0,sizeof(vis));

        clr.clear();
        for(i=1;i*i<=m;i++){
            //cout << i << endl;
            if(m%i==0){
                //cout << i << endl;
                clr.push_back(i);
                if(i*i!=m)
                    clr.push_back(m/i);
            }
        }
        sort(clr.begin(),clr.end());
//    cout << clr.size() << endl;

        for(i=1;i<=n;i++){
            scanf("%lld",&Arr[i]);
            Arr[i] = gcd(Arr[i],m);
            //cout << Arr[i] << endl;
            for(j=0;j<clr.size();j++){
                if(clr[j]%Arr[i]==0){
                    vis[j]=1;
                }
            }
        }

        vis[clr.size()-1]=0;
        for(i=0;i<clr.size();i++){
            if(num[i]!=vis[i]){
                LL t = m/clr[i];
                ans += t*(t+1)/2*clr[i]*(vis[i]-num[i]);
                t = vis[i]-num[i];
                for(j=i+1;j<clr.size();j++){
                    if(clr[j]%clr[i]==0){
                        num[j] += t;
                    }
                }
            }
        }
        printf("Case #%d: %lld\n",++cas,ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值