codeforces837E Vasya's Function

首先在下参考了http://blog.youkuaiyun.com/calabash_boy/article/details/76652792
以及submit#29208538
数论什么的最讨厌了……


设f(x,y),若y=0,则f(x,0)=0,否则f(x,y)=f(x,y-gcd(x,y))。
数据范围是1<=x,y<= 1012 所以不能指望暴力(没错,3点就会T)
那么来分析一下。
不妨设x=A·gcd(x,y),y=B·gcd(x,y)。那么,一次操作后,x=A·gcd(x,y),y=(B-1)·gcd(x,y),显然gcd(x,y)是两个的因数之一。现在我们进行k次操作,x=A·gcd(x,y)不变,y=(B-k)·gcd(x,y),假设此时恰好gcd(A·gcd(x,y),(B-k)·gcd(x,y))改变,就意味着:gcd(A,B-k)不为1.
现在的问题就是如何求出k,因为如果知道k,我们就可以很快速的求出操作的次数。如果枚举的话一定会T,我们来稍微变个形:假定r为A的一个因子,那么(B-k)%r=0.不妨设(B-k)=q*r,则B=q*r+k,即B%r=k%r.因为k一定小于r,因为如果k大于等于r,一定存在k-r使(B-(k-r))%r=0,这与k最小相矛盾。所以,B%r=k。我们只需要在O( n )的时间内枚举出所有的因子,然后跑就可以。
这样的话思路的确是正确的,不过12点就T了。仔细想了一下,这样写会反复调用查找因子的函数,效率会非常低。那如何改进呢?
我研究了半天别人的代码,发现了一个非常巧妙的办法。首先我们先让x,y除gcd(x,y),此时的x的所有因子没有公共因子。那么我们就可以只枚举出此时x所有素数因子。
而由于x为定值,我们就可以在每一次操作之后的筛选中从已经筛选过的因子中再次筛选,如果某个因子是操作后y的因子,那么就不列为下次操作的考虑对象。否则就让y除以这些因子,因为这就相当于让y除以gcd(x,y).

#include <iostream>
#include <vector>
#include <algorithm>
#define MAXN 1e14
typedef long long LL;
using namespace std;
LL x,y,g,t;
vector<LL> va;
vector<LL>::iterator it;
LL gcd(LL x,LL y){
    return y==0?x:gcd(y,x%y);
}
LL init(){
    g=gcd(x,y);
    x=x/g,y=y/g;
    for(LL i=2;i*i<=x;++i){
        while(x%i==0){
            va.push_back(i);
            x/=i;
        }                   
    }
    if(x>1) va.push_back(x);
}
void work(LL y){
    if(y==0) return;
    LL k=y;
    for(it=va.begin();it!=va.end();++it){
        k=min(k,y%(*it));
    }   
    t+=k,y-=k;
    vector<LL> vb;
    for(it=va.begin();it!=va.end();++it){
        if(y%(*it)!=0) vb.push_back(*it);
        else y/=(*it);
    }
    swap(va,vb);
    work(y);
}
int main(){
    cin>>x>>y;
    init();
    work(y);
    cout<<t;
}

数论太难了orz……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值