[OJ]信仰之跃、互质、求解同余方程、新世纪线段覆盖

1.信仰之跃

如图,一名刺客怀着信条,要进行他的信仰之跃。下面有个草垛,他必须跳到草垛里,不然会摔死。刺客可以看成一个质点,给出高度h和草垛距离前方刺客距离d的范围,请你计算他的初速度v(水平方向)的范围。(刺客在M星球,M星球的重力加速度g为18m/s^2)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
图片来源:平抛运动8个公式是什么?
本题中只需要用到竖直位移和水平位移。对于本题来说,重要的是分数的表示。
分数的表示,参考:C++ 关于分数的处理与计算
其主要思想是用一个类表示分数,一个属性表示分子,另一个属性表示分母。再进行加减乘除。

/*
 * @Author: error: git config user.name && git config user.email & please set dead value or install git
 * @Date: 2022-08-05 15:15:22
 * @LastEditors: error: git config user.name && git config user.email & please set dead value or install git
 * @LastEditTime: 2022-08-05 18:30:35
 * @FilePath: \C++\testC++\JumpWall.cpp
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#include<iostream>
#include<cmath>
#include<vector>
using namespace std;
const int  g=18;



class Assassin{
private:
    int x1;//水平方向区间的开始
    int x2;//区间结束
    int y;//垂直方向
public:
    Assassin(int x1,int x2,int y);
    int X1Get();
    void X1Set(int x1);
    int X2Get();
    void X2Set(int x2);
    int YGet();
    void YSet(int y);
    ~Assassin(){};
};

struct Fraction{
    int up;//分子
    int down;//分母
    Fraction(){};
    Fraction(int _up,int _down)
    {
        this->up = _up;
        this->down = _down;
    }
};

Fraction f1;
Fraction f2;
vector<Fraction> v1;//存放区间

//辗转相除法,递归,导致时间复杂度太大
int gcd(int a,int b)
{  
    if(b==0)
    {
        return a;
    }
    else
    {
        return gcd(b,a%b);
    }
    
}

//分数的化简
Fraction reduction(Fraction result)
{
    if(result.down < 0)
    {
        result.up *= (-1);
        result.down *= (-1);
    }
    if(result.up == 0)
        result.down = 1;
    int _gcd = gcd(abs(result.up),abs(result.down));
    result.up = result.up/_gcd;
    result.down = result.down/_gcd;
    return result;
}

Assassin::Assassin(int x1,int x2,int y)
{
    this->x1=x1;
    this->x2=x2;
    this->y=y;
}

int Assassin::X1Get()
{
    return x1;
}
    
void Assassin::X1Set(int x1)
{
    this->x1=x1;
}
int Assassin::X2Get()
{
    return x2;
}
    
void Assassin::X2Set(int x2)
{
    this->x2=x2;
}
int  Assassin::YGet()
{
    return y;
}

void Assassin::YSet(int y)
{
    this->y=y;
}

Fraction divide(Fraction f1 , Fraction f2)
{
    Fraction f3;
    if(f2.up == 0)
        return f2;//分母为0
    f3.up = f1.up*f2.down;
    f3.down = f1.down*f2.up;
    f3 = reduction(f3);
    return f3;
}

void print(Fraction f1,Fraction f2)
{
    if(f1.down==1 && f2.down==1)//如果分母是1,则当做整数输出
        {
            cout<<f1.up<<" "<<f2.up<<endl;
        }
        else if(f1.down==1 && f2.down>1)
        {
            cout<<f1.up<<" "<<f2.up<<"/"<<f2.down<<endl;
        }
        else if(f1.down>1 && f2.down==1)
        {
            cout<<f1.up<<"/"<<f1.down<<" "<<f2.up<<endl;
        }
        else
        {
            cout<<f1.up<<"/"<<f1.down<<" "<<f2.up<<"/"<<f2.down<<endl;
        }
}

void CalV(int &d1,int &d2,int &h)
{
    Assassin assassin(d1,d2,h);
    Fraction t;//表示t
    //根据y=1/2gt^2,求t,t可能为分数
    //int t=int(sqrt(2*assassin.YGet()/g));
    t.up=int(sqrt(assassin.YGet()));
    t.down=sqrt(g/2);
    t=reduction(t);//化简

    //根据x=vt,求v,v可能为分数
    
    //将f1(x1)看做一个分数
    f1.up=assassin.X1Get();
    f1.down=1;
    //v=x/t
    f1=divide(f1,t);

    f1=reduction(f1);//约分
    v1.push_back(f1);

    //f2(x2)
    f2.up=assassin.X2Get();
    f2.down=1;
    f2=divide(f2,t);
    f2=reduction(f2);//约分
    v1.push_back(f2);
}

int  main()
{
    int T;
    cin>>T;
    
    int h,d1,d2;
    for(int i=0;i<T;++i)
    {
        cin>>h>>d1>>d2;
        CalV(d1,d2,h);
    }
    //正向输出所有元素
    vector<Fraction>::iterator it;
    for(it=v1.begin();it!=v1.end();it+=2)
    {
        print(*it,*(it+1));
    }
    return 0;
}

在这里插入图片描述
在这里插入图片描述

2.互质

输入两个正整数n和k,求与n互质的第k个正整数。
在这里插入图片描述

/*
 * @Author: error: git config user.name && git config user.email & please set dead value or install git
 * @Date: 2022-08-04 16:47:37
 * @LastEditors: error: git config user.name && git config user.email & please set dead value or install git
 * @LastEditTime: 2022-08-04 23:28:24
 * @FilePath: \C++\testC++\PrimeNumber.cpp
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#include<iostream>
using namespace std;
const int maxn=1000006;
int prime[maxn],num=0;

/*
采用欧拉函数进行求取
对正整数n,欧拉函数是小于等于n的正整数中与n互质的数的数目.
欧拉函数Euler(n):求[2,n]中有多少个数与n互素

直接利用公式:φ(x)=x(1-1/p1)(1-1/p2)(1-1/p3)(1-1/p4)…..(1-1/pn)

其中:

pi为x的素因数
每个素因数只用一次
比如90=2 * 3^2 * 5
φ(90) = 90 * (1-1/2) * (1-1/3) * (1-1/5)
φ(1)=1(唯一和1互质的数(小于等于1)就是1本身)
如果不同的整数X和Y的最⼤公约数为1,则称X和Y为互质数。
Y的最⼤公约数为1,则称X和Y为互质数。显然,X和Y互质等价于Y和X互质。给出整数N,求1~N之间,有多少个数对
(X,Y)是互质数。
例如N=5时,(1,2) , (1,3), (1,4), (1,5), (2,3), (2,5), (3,4), (3,5), (4,5)共9对数是互质数。
*/

//辗转相除法,递归,导致时间复杂度太大
int gcd(int a,int b)
{  
    if(b==0)
    {
        return a;
    }
    else
    {
        return gcd(b,a%b);
    }
    
}

/*
由于当a与b互质时,a+b与b互质,可以求出a以内的互质的数,
由周期性推出剩下所有的与a互质的数
*/
int FindK(int n,int k)
{
    for(int i=1;i<n;i++)
    {
        if(gcd(n,i)==1)
        {
            prime[++num]=i;
        }
    }
   return k/num*n+prime[k%num];
}

int  main()
{
    int n,k;
    cin>>n>>k;
    int result=FindK(n,k);
    cout<<result;
    return 0;
}

在这里插入图片描述
在这里插入图片描述
参考:洛谷P1592 互质

3.求解同余方程

对于同余方程,求解它的最小整数解x0
在这里插入图片描述
方案:采用扩展欧几里得,即辗转相除法

/*
 * @Author: error: git config user.name && git config user.email & please set dead value or install git
 * @Date: 2022-08-05 09:42:32
 * @LastEditors: error: git config user.name && git config user.email & please set dead value or install git
 * @LastEditTime: 2022-08-05 10:31:03
 * @FilePath: \C++\testC++\congruentEquation.cpp
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#include<iostream>
using namespace std;

/*
 扩展欧几里得是用来求这样的一组解的:ax + by = gcd(a, b),求x和y,
 
    (求出x后自然知道了y,所以算是求一个x)
    逆元呢是求这样的一个解:ax ≡ 1 (mod b),(为了方便理解,改了一下变量名),
    求x,貌似并没有一点点相似处,那么我们变一下,
    ax+by = gcd(a,b),变成 ax+by = c;
    ax ≡ 1 (mod b),变成 ax-by = 1;如果将y看成负的,ax+by = 1;
    完全一样,所以直接套用扩展欧几里得求
*/

int exgcd(int a,int b,int &x,int &y)
{
    //gcd(a,b)表示a和b的最大公约数,0与一个数的最大公约数是这个数本身
    //此时,毫无疑问gcd(a,b) == a,此时存在解x = 1,y = 0;
    if(b==0)
    {
        y=0;
        x=1;
        return a;
    }
    int r=exgcd(b,a%b,x,y);
    /*
       '''
    b*x1 + (a%b)*y1 = gcd
    由 a%b = a - (a/b)*b(这里的 “/” 指的是整除,例如 5/2=2 , 1/3=0)可得
    gcd = b*x1 + (a-(a/b)*b)*y1
        = a*y1 + b*(x1 – a/b*y1)
    所以
    x = y1
    y = x1 – a/b*y1
    '''
    */
    int temp=x;
    x=y;
    y=temp-a/b*y;
   
    return r;

}

int  main()
{
    int a,b,x,y;
    cin>>a>>b;
    int num=exgcd(a,b,x,y);
    cout<<(x%b+b)%b<<endl;
    return 0;
}

在这里插入图片描述
在这里插入图片描述

4.新世纪线段覆盖

现给定一根坐标轴,坐标轴上有n条线段,每条线段从1开始编号,若线段编号为k,则该线段覆盖的区间是:[(k-1)×m,(k-1)×m+l]。
有一个质点从原点开始向x轴正半轴移动,每次移动d距离。问第一个不被线段覆盖的点的位置在哪里?
在这里插入图片描述

对于该题来说,暴力解法的时间复杂度相当高,因此采用区间求解。
以样例1为例:此时n=5,d=4,m=11,l=8;
对于1到n的区间内,从1开始遍历到n,
当k=1时,[(k-1)*m,(k-1)*m+l]为[0,8];
当k=2时,[(k-1)*m,(k-1)*m+l]为[11,19];
当k=3时,[(k-1)*m,(k-1)*m+l]为[22,30];

因此,只需要判断(8,11)(19,22)之间的数是否是d的倍数(注意,这里是开区间),仅且判断一次,如果是,这直接跳出循环,返回数字。
当超出n的时候,例如n=2,d=4,m=11,l=8;
则用(n-1)*m+l来对d取余,设j=((n-1)*m+l)%d,那么还差d-j个数便是下一个d的倍数。
代码如下:

/*
 * @Author: error: git config user.name && git config user.email & please set dead value or install git
 * @Date: 2022-08-05 19:04:25
 * @LastEditors: error: git config user.name && git config user.email & please set dead value or install git
 * @LastEditTime: 2022-08-05 20:05:51
 * @FilePath: \C++\testC++\LineCoverage.cpp
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#include<iostream>
using namespace std;

/*
每次移动d,记录x当前的位置
如果采用暴力法,时间复杂度很高
*/
int LineCoverage(int n,int d,int m,int l)
{
    if(n==1)
    {
         //如果超出n条线段的最大区间
        int num=((n-1)*m+l)%d;
        return ((n-1)*m+l)+(d-num);
    }
    else if(n>1)
    {
        //当n>1时
        for (int i=1;i<n;i++)
        {  
            //在区间((i-1)*m+l)--((i+1-1)*m)中的数,是否是d的倍数
            //如果是,则寻找超出((i-1)*m+l)的第一个数
            for(int k=((i-1)*m+l)+1;k<((i+1-1)*m);k++)
            {
                if(k%d==0)
                {
                    return k;
                }
            }     
            
        }
        //如果超出n条线段的最大区间
        int num=((n-1)*m+l)%d;
        return ((n-1)*m+l)+(d-num);
    }
    return 0;
}


int  main()
{
    int n,d,m,l;
    cin>>n>>d>>m>>l;
    int result=LineCoverage(n,d,m,l);
    cout<<result<<endl;
    return 0;
}

在这里插入图片描述
在提交时,有一处错误,应该是有种情况没有考虑到
在这里插入图片描述
经过反复检查发现当n,d,m,l取1e6时,超出int范围,导致错误发生,因此修改变量类型,改为long long ,代码如下:

/*
 * @Author: error: git config user.name && git config user.email & please set dead value or install git
 * @Date: 2022-08-05 19:04:25
 * @LastEditors: error: git config user.name && git config user.email & please set dead value or install git
 * @LastEditTime: 2022-08-05 22:13:14
 * @FilePath: \C++\testC++\LineCoverage.cpp
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#include<iostream>
using namespace std;

/*
每次移动d,记录x当前的位置
如果采用暴力法,时间复杂度很高
*/
long long LineCoverage(long long n,long long d,long long m,long long l)
{
    if(n==1)
    {
         //如果超出n条线段的最大区间
        int num=((n-1)*m+l)%d;
        return ((n-1)*m+l)+(d-num);
    }
    else if(n>1)
    {
        //当n>1时
        for (int i=1;i<n;i++)
        {  
            //在区间((i-1)*m+l)--((i+1-1)*m)中的数,是否是d的倍数
            //如果是,则寻找超出((i-1)*m+l)的第一个数
            for(long long k=((i-1)*m+l)+1;k<((i+1-1)*m);k++)
            {
                if(k%d==0)
                {
                    return k;
                }
            }     
            
        }
        //如果超出n条线段的最大区间
        int num=((n-1)*m+l)%d;
        return ((n-1)*m+l)+(d-num);
    }
    return 0;
}


int  main()
{
    long long n,d,m,l;
    cin>>n>>d>>m>>l;
    long long result=LineCoverage(n,d,m,l);
    cout<<result<<endl;
    return 0;
}

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值