2018-01-15 HDU 5514 Ver.B 假容斥原理 容斥原理

本文针对一组青蛙在圆周上的跳跃问题进行了深入分析,通过数学方法简化了问题,并提供了一个高效的解决方案。利用等差数列求和及因子分解,解决了哪些石头会被青蛙占据的问题。

题目描述:

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.

数据输入:

There are multiple test cases (no more than 20), and the first line contains an integer t,meaning the total number of test cases.

For each test case, the first line contains two positive integer n and m - the number of frogs and stones respectively (1≤n≤104, 1≤m≤109).

The second line contains n integers a1,a2,⋯,an, where ai denotes step length of the i-th frog (1≤ai≤109).

数据输出:

For each test case, you should print first the identifier of the test case and then the sum of all occupied stones' identifiers.

样例输入:

3

2 12

9 10

3 60

22 33 66

9 96

81 40 48 32 64 16 96 42 72

样例输出:

Case #1: 42

Case #2: 1170

Case #3: 1872

题目的解释可以看我之前的Ver.A, 还有一些基本的操作, 比如实际的等效步数是青蛙步数和石头个数m的gcd, 找到等效步数是1的青蛙之后的特判.

然后我们用另一种角度来看这个题目.

首先我们可以从因子的角度来看序列和怎么算, 比如我们有一只等效步长为k的青蛙,恰巧我们又有m块石头, 根据k的来源我们可以知道k一定是m的因子, 那么这只青蛙的贡献就是1*k+2*k+3*k+.....+m, 就是k*(1+2+3+.....+m/k), 这是一个简单的等差数列求和.

然后还有一个有趣的事实就是, 举个例子, 我们有一只等效步长为2的青蛙和一只等效步长为4的青蛙, 如果我们排完序之后, 先计算了等效步长为2的青蛙的贡献, 那就没有必要计算等效步长为4的青蛙的贡献了---因为步长为4的青蛙跳的石头, 步长为2的青蛙也一定会跳到. 换句话说, 你把步长为2的青蛙的贡献计算过后, 步长分别为4,6,8,10,12,......的青蛙的贡献统统都不用计算了.所以下面的代码里面, 当我把k步青蛙的贡献给算了之后, 2k,3k,之类的都要更新一遍.

至于为什么这被叫做假容斥,主要是这里用的并不是模板上面的思想, 我也讲不清楚, 举个例子好了.

比如我们先算了一只步长为2的青蛙的贡献, 然后我们又算了一遍步长为3的青蛙的贡献, 我们可以发现, 步长为6,12,18,....的青蛙的贡献被算了两遍---很像容斥里面的多算了, 所以我们要减掉多出的那一部分, 就是代码里面变量change的意义.

然后就是要有哪些因子是要计算贡献的, 先给m进行因子的分解.正如前一篇所说, 等效步长为m的青蛙没有意义, 这就是为什么里面会有那么多"fac_num-1". 最后再为每一个因子找青蛙---如果找到了, 就把它应该贡献的次数变成1, 没有的话就是0, 也别问我为什么不是2次3次,因为序号只计一次啊....

找完了, 就计算每个因子的贡献就行了, 记得计完因子k后把2*k,3*k,4*k,.....的也更新一下. 如果多出来就减, 少了加上就行.

代码如下:

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;

long long int step[10010];
long long int add_times[1000005];
long long int need_times[1000005];
long long int fac[1000005];

long long int _gcd(long long int a,long long int b)
{
    long long int res;
    res=a%b;
    while(res)
    {
        a=b;
        b=res;
        res=a%b;
    }
    return b;
}

int main()
{
    long long int res,temp,m,fac_num,i,j,change;
    bool all;
    long long int T,case_num,n;
    scanf("%lld",&T);
    case_num=1;
    while(T--)
    {
        fac_num=0;
        res=0;
        all=false;
        memset(need_times,0,sizeof(need_times));
        memset(add_times,0,sizeof(add_times));
        scanf("%lld%lld",&n,&m);
        for(i=0;i<n;i++)//读入青蛙的步数
        {
            scanf("%lld",&temp);
            step[i]=_gcd(temp,m);
            if(step[i]==1)
                all=true;
        }
        if(all==true)
        {
            printf("Case #%lld: %lld\n",case_num++,(m-1)*m/2);
            continue;
        }
        sort(step,step+n);
        n=int(unique(step,step+n)-step);//经典的去重函数unique
        for(i=1;i*i<=m;i++)
        {
            if(i*i==m)
            {
                fac[fac_num++]=i;
            }
            else if(m%i==0)
            {
                fac[fac_num++]=i;
                fac[fac_num++]=m/i;
            }
        }
        sort(fac,fac+fac_num);
        res=0;
        for(i=0;i<fac_num-1;i++)
        {
            for(j=0;j<n;j++)
            {
                if(fac[i]%step[j]==0)
                {
                    need_times[i]=1;
                    break;
                }
            }
            if(add_times[i]!=need_times[i])
            {
                temp=m/fac[i];
                res+=temp*(temp-1)/2*fac[i]*(need_times[i]-add_times[i]);
                change=need_times[i]-add_times[i];
                for(j=i;j<fac_num-1;j++)
                {//举个例子:步长为2的青蛙会把4,6,8,10,....的给跳了,就是把它们的贡献都给算了一遍
                    if(fac[j]%fac[i]==0)
                    {
                        add_times[j]+=change;
                    }
                }
            }
        }
        printf("Case #%lld: %lld\n",case_num++,res);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值