1. 程式人生 > >HDU5514 變形巧妙運用容斥定理

HDU5514 變形巧妙運用容斥定理

There are mm stones lying on a circle, and nn frogs are jumping over them.  The stones are numbered from 00 to m−1m−1 and the frogs are numbered from 11 to nn. The ii-th frog can jump over exactly aiai stones in a single step, which means from stone j mod mj mod m to stone (j+ai) mod m(j+ai) mod m (since all stones lie on a circle).  All frogs start their jump at stone 00, 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.

Input

There are multiple test cases (no more than 2020), and the first line contains an integer tt,  meaning the total number of test cases.  For each test case, the first line contains two positive integer nn and mm - the number of frogs and stones respectively (1≤n≤104, 1≤m≤109)(1≤n≤104, 1≤m≤109).  The second line contains nn integers a1,a2,⋯,ana1,a2,⋯,an, where aiai denotes step length of the ii-th frog (1≤ai≤109)(1≤ai≤109).

Output

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

Sample Input

3
2 12
9 10
3 60
22 33 66
9 96
81 40 48 32 64 16 96 42 72

Sample Output

Case #1: 42
Case #2: 1170
Case #3: 1872
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define init(a,b) memset(a,b,sizeof a)
const int MAXN = 1e4+10;
int a[MAXN];
int d[MAXN];
int vis[MAXN];
int num[MAXN];
//要求所有frogs跳過的石頭的序號,那麼跳過的石頭一定都是gcd(a[i],m)的倍數,最大就能跳到m-1的石頭
//為什麼,你可以模擬一下,大於m的時候取一下模就得出,比如第一組樣例 12 9 那麼跳過的石頭的序號排列
//之後就是 3 6 9 12(0), 但是gcd(9,12)=3與gcd(10,12)=3的倍數中有重複的數,那就需要去重,怎麼去,看一下
//所有被踩過的石頭的序號構成一個等差數列,個數為m/gcd(a[i],m),公差為gcd(a[i],m);這樣就能求出每一個因子
//所到達的序列總和,然後剩下的就是去重,下面要引入一個名稱,其他部落格裡面寫的叫做貢獻次數,那我就也叫貢獻次數
//下面就結合程式碼解釋
int main()
{
    int t;
    int n, m;
    freopen("in.txt","r",stdin);
    scanf("%d", &t);
    for(int cas = 1; cas <= t; cas++) {
        init(vis,0);init(num,0);
        int cnt=0;
        scanf("%d%d",&n,&m);
        for(int i=1;i*i<=m;i++)//先將所有的m裡面的因子找到,為下面說明哪些因子用到了
        {
            if(m%i==0){
                d[cnt++]=i;
            if(i*i!=m)
                d[cnt++]=m/i;
            }
        }
        sort(d,d+cnt);//sort一下便於下面的計算,這樣2的倍數6的倍數好處理
        rep(i,0,n-1)
        {
            scanf("%d",&a[i]);
            a[i]=__gcd(a[i],m);
            rep(j,0,cnt-1){
            if(d[j]%a[i]==0) vis[j]=1;//說明b[j]裡面的因子是gcd的倍數,那麼就要看下面的要不要去重了,
            //這是為下面num陣列結合所用的
            }
        }
        LL ans=0;
        //num陣列存放的是第i個因子(指再d數組裡面存放的)的貢獻次數值,開始初始化為0,然後從最小的
        //因子開始求所能到達的石頭的序列的和,如果沒有該因子,那都是0,就不會進入下面求和的步驟,或者
        //當所有的a[i]與m的gcd沒有相同的因子時也不會有求和的步驟,比如第一組資料3與2中,2的倍數4就不會再次求和
        //步驟,但時當兩者有相同的公倍數那就需要去重了
        rep(i,0,cnt-1)
        {
            if(vis[i]!=num[i]){
                LL t=m/d[i];//當因子為d[i]時,能夠跳的石頭的個數
                ans+=t*(t-1)/2*d[i]*(vis[i]-num[i]);//後面的vis[i]-num[i]是看兩者是否有多次貢獻
//在第一組樣例中6就被用了兩次求和,那就需要減去,減去就是加上負數vis[i]-num[i];
//完成去重,因此只改變num[i]的數就能知道因子d[i]被重複用了多少次,下面是對num修改
                for(int j=i+1;j<cnt;j++)
                    if(d[j]%d[i]==0)//num最大為2
                        num[j] +=vis[i]-num[i];
            }
        }
        printf("Case #%d: %I64d\n", cas, ans);
    }
    return 0;
}