『HDU 5970』最大公约数

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5970

题意:(中文题,不用我说了吧.)
这里写图片描述

分析:
当时在比赛的时候并没有做出来,主要是时间不够,有点分心在其他题上,然后就水了.其实看到这种题,第一想法不用想太复杂,可能有暴力的方法,找规律!
看看数据范围
n <= 666,666,666, m <= 666, p <= 666,666,666
突破口必然在m上,那怎么用呢?
不可能暴力gcd和T的,这样必然超时,我们知道gcd(a,b)=gcd(b,a%b)
那又怎么样呢?那么给你一个提示,例如gcd(5,9)=gcd(16,9)
手写一下,你就发现确实是这样啊~!
不仅如此,f(5,9)=f(16,9)
那么我们就知道,如果以m为基数的话,那么对于所有的n,莫非就余0,余1,余2…余m-1
我们就可以这样做,然后就可以统计同余的有多少个,然后看一下规律,然后一大片的求,
就能在m^2的时间求出答案了…
别住,这个规律还是有问题啊.
它有向下取整,不可能把个i什么的拆出来,就必须一个个才行.就是**(i*j)/f(i,j)**.
那就会出现多1,少1的情况…因为取整问题…

那么我们先暴力一段序列.看看数据有什么问题.
例如 9余5的所有数的f(i,j)
这里写图片描述
这明显有规律啊,循环节啊…

这下就懵逼了,循环节该怎么搞啊?
他们的差值又不一样…,这是个好问题,但是既然是循环节,肯定等差..

这里写图片描述
我也写了很久,然后我灵感来的猜出来的.
除去第一个f(y,m)=11;
他的差值循环节就是c
然后 d=c(f(y,m)=11)(代表c倍的,余数y和m的f).*
以上是我找到的规律,为什么是这样我就不太懂了,我也想了很久.
只要我们求出 **(31+51+72+92)**这段的初值,就可以用等差数列求循环节一整段
然后就是细节的处理了.

我的程序是卡过这题的,我也不知道为什么那么慢
算法复杂度在O(m^2*c) 我试过**On(m^2*2c)**都不行,因为这个循环节规律就是逼我在这种情况下找出来的.

/* Author:GavinjouElephant
 * Title:
 * Number:
 * main meanning:
 *
 *
 *
 */


#include <iostream>
using namespace std;
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <sstream>
#include <cctype>
#include <vector>
#include <set>
#include <cstdlib>
#include <map>
#include <queue>
//#include<initializer_list>
//#include <windows.h>
//#include <fstream>
//#include <conio.h>
#define MaxN 0x7fffffff
#define MinN -0x7fffffff
#define lson 2*k
#define rson 2*k+1
typedef long long ll;
const ll INF=0x3f3f3f3f;
const ll maxn=700;
ll Scan()//读入整数外挂.
{
    ll res = 0, ch, flag = 0;

    if((ch = getchar()) == '-')             //判断正负
        flag = 1;

    else if(ch >= '0' && ch <= '9')           //得到完整的数
        res = ch - '0';
    while((ch = getchar()) >= '0' && ch <= '9' )
        res = res * 10 + ch - '0';
    return flag ? -res : res;
}
void Out(ll a)    //输出外挂
{
    if(a>9)
        Out(a/10);
    putchar(a%10+'0');
}
ll Fc[maxn][maxn];
ll Fcx[maxn][maxn];


void  f(ll x,ll y)
{
    ll tx=x,ty=y;
    ll c=0;
    while(y>0)
    {
        c+=1;
        ll t=x%y;
        x=y;
        y=t;
    }

    Fc[tx][ty]=c;
    Fcx[tx][ty]=c*x*x;

}

void init()
{

    for(ll j=1; j<=666; j++)
    {
        for(ll i=0; i<=j; i++)
        {
            f(i,j);
        }
    }
}


ll cas(ll x,ll y)
{
    return (x*y)/Fcx[x%y][y];
}

int T;
ll n,m,p;
ll top;
ll xhj[5000];
ll Sum[5000];

int main()
{
#ifndef ONLINE_JUDGE
    freopen("coco.txt","r",stdin);
    freopen("lala.txt","w",stdout);
#endif

    init();
    scanf("%d",&T);
    while(T--)
    {
        scanf("%I64d%I64d%I64d",&n,&m,&p);
        ll ans=0;
        ll d;

        for(ll j=1; j<=m; j++)
        {
            for(ll y=0; y<j; y++)
            {
                ll  l=1-j-y;
                ll  r=n-y;
                ll num=(r/j - l/j);

                if(y==0)
                {
                    ans=(ans+num%p+(num*(num-1)/2)%p)%p;
                }
                else
                {
                    //-->找循环节
                    if(num==0) continue;

                    ll t=(num-1)/Fc[y][j];
                    ll tt=(num-1)%Fc[y][j];
                    ll s=cas(y,j);

                    ans=(ans+s)%p;

                    ll ss=0;
                    ll sum=0;
                    top=0;

                    int i=y+j;
                    for(top=0;top<Fc[y][j];top++,i+=j)
                    {
                        ll k=cas(i,j)-cas(i-j,j);
                        sum=(sum+k)%p;
                        xhj[top]=k;

                        if(top==0){Sum[top]=k;}
                        else {Sum[top]=Sum[top-1]+sum;}
                    }

                    if(t)
                    {   ss=(Sum[top-1]+s*top)%p;
                        d=(Fc[y][j]*sum)%p;
                        ans=(ans+(ss*t)%p+(t*(t-1)/2*d)%p)%p;
                        s=(s+(sum*t))%p;
                    }


                    if(tt){ans=(ans+Sum[tt-1]+(s*tt)%p)%p;}


                }

            }

        }
        printf("%I64d\n",(ans+p)%p);

    }



    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值