2023NEUQACM Week1

必做题


P8682 [蓝桥杯 2019 省 B] 等差数列

快速过一下题目:求包含已知数列的最小等差数列

假设这个最小等差数列的公差为 d d d
对于已知数列 A 1 , A 2 , ⋯   , A N A_1,A_2,\cdots,A_N A1,A2,,AN
A i − A i − 1 = n ∗ d ( 1 ≤ i ≤ N , n = 0 , 1 , 2 , 3 ⋯   ) A_i-A_{i-1}=n*d (1\le i \le N,n =0,1,2,3 \cdots) AiAi1=nd(1iN,n=0,1,2,3)
故此题为求数列 A 1 , A 2 , ⋯   , A N A_1,A_2,\cdots,A_N A1,A2,,AN 的最大公约数

对于整数 ( a , b , c ) (a,b,c) (a,b,c) g c d ( a , b ) , g c d ( a , b , c ) gcd(a,b),gcd(a,b,c) gcd(a,b),gcd(a,b,c) ( a , b ) , ( a , b , c ) (a,b),(a,b,c) (a,b),(a,b,c)的最大公约数,自然有 g c d ( a , b , c ) = g c d ( g c d ( a , b ) , c ) gcd(a,b,c) = gcd(gcd(a,b),c) gcd(a,b,c)=gcd(gcd(a,b),c)

将数组排好序之后,求出所有相邻项差的最大公约数

// #include<algorithm>
sort(arr, arr+n);
d = arr[1] - arr[0];
for(int i=2 ; i<n ; i++){
    d = __gcd(d, arr[i] - arr[i-1]);
}

完整代码如下:

#include<iostream>
#include<algorithm>
using namespace std;

/*
// 也可以手搓一个gcd函数
int gcd(int a, int b)
{
    if(b==0) return a;
    return gcd(b, a%b);
}
*/
int main()
{
    // freopen("D:\\test.txt", "r", stdin);
    int n,d;
    cin >> n;
    int arr[n+10];
    for(int i=0 ; i<n ; i++)
        cin >> arr[i];
    sort(arr, arr+n);
    d = arr[1] - arr[0];
    if(d == 0)
        cout << n;
    else{
        for(int i=2 ; i<n ; i++)
            d = __gcd(d, arr[i] - arr[i-1]);
        cout << (arr[n-1]- arr[0])/d + 1;
    }
    return 0;
}

P1226 【模板】快速幂

题目很简单:给你三个整数 a , b , c a, b, c a,b,c,求 a b   m o d    p a^b\ mod\ \ p ab mod  p

思路:快速幂,对每一次的结果取模

#include<iostream>
using namespace std;

typedef long long ll; // 开long long不然会爆
ll qPower(ll a, ll b, ll p)
{
    ll ans;
    if(b == 0) ans=1;
    else{
        ans = qPower(a*a%p, b/2, p); // 这里a*a必须取模
        if(b%2) ans *= a;
        ans %= p; // 答案取模
    }
    return ans;
}
int main()
{
    // freopen("D:\\test.txt", "r", stdin);
    ll a, b ,p;
    scanf("%lld%lld%lld", &a, &b, &p);
    printf("%lld^%lld mod %lld=%lld", a,b,p,qPower(a,b,p));
    return 0;
}

P2249 【深基13.例1】查找

题目大意:在一个单调递增的数列中找到一个数第一次出现的位置

关键: 找到一个数的最小位置
思路: 如果这个数存在,二分查找到这个数的位置并记录,再在这个数的前面尝试能不能再找到这个数,如果能,更新它的位置。

代码实现:

#include<iostream>
#include<algorithm>
using namespace std;

int arr[1000011];
int lb(int a[], int n, int M)
{
    int ans = -1; // 如果这个数不存在,ans不会被更新
    int left = 1, right = n;
    while(left <= right){
        int mid = left+(right-left)/2;
        if(a[mid] > M) 
            right = mid-1;
        else if(a[mid] < M) 
            left = mid+1;
        else{
            ans = mid;
            right = mid-1;
        }
    }
    return ans;
}
int main()
{
    // freopen("D:\\test.txt", "r", stdin);
    int n,m,M;
    scanf("%d%d", &n, &m);
    for(int i=1 ; i<=n ; i++)
        scanf("%d", &arr[i]);
    for(int i=0 ; i<m ; i++){
        scanf("%d", &M);
        int ans = lb(arr,n,M);
        printf("%d ", ans);
    }
    return 0;
}

P1824 进击的奶牛

题目大意:在 N N N 个位置放 C C C 个元素,找到放置每个元素的最大最短距离 a n s ans ans

思路:二分查找到这个最大最短距离
关键:找到了一个距离的时候,如何判断它是否是最大最短距离?

设这 N N N 个位置中第 k k k 个位置为 P k P_k Pk,定义一个函数 isAns,以我们得到的这个距离为最短距离从 P 1 P_1 P1 开始放置元素,看能不能放完。如果能,记录下这个距离并在更大的范围内尝试找到更大的最短距离;如果不能,在更小的范围内继续搜索。

bool isAns(int a[], int N, int C, int d) // 在有N个元素的数组a中以距离d放置C个元素
{
    --C; // 第一个位置肯定要放一个元素
    int i=0, j=1;
    while(j<N){
        if(a[j]-a[i] < d) // 说明在位置i放了一个元素后,在位置j放不了
            j++;
        else{ 
            C--;
            i=j; 
            j++;
        } // 在位置j放了一个元素
    }
    if(C>0) return false;
    else return true;
}

main函数里只要以 P N − P 1 P_N-P_1 PNP1为最大距离,在 d   ϵ   [ 1 , P N − P 1 ] d\ \epsilon\ [1,P_N-P_1] d ϵ [1,PNP1] 的范围内二分查找即可
完整代码如下:

时间复杂度 O ( N ∗ l o g N ) O(N*logN) O(NlogN)

#include<iostream>
#include<algorithm>
using namespace std;

bool isAns(int a[], int N, int C, int d) // 在有N个元素的数组a中以距离d放置C个元素
{
    --C; // 第一个位置肯定要放一个元素
    int i=0, j=1;
    while(j<N){
        if(a[j]-a[i] < d) // 说明在位置i放了一个元素后,在位置j放不了
            j++;
        else{
            C--;
            i=j; 
            j++;
        } // 在位置j放了一个元素
    }
    if(C>0) return false;
    else return true;
}
int arr[100011];
int main()
{
    // freopen("D:\\test.txt", "r", stdin);
    int N, C, left, right, mid, ans;
    cin >> N >> C;
    for(int i=0 ; i<N ; i++)
        cin >> arr[i];
    sort(arr, arr+N);
    left = 1, right = arr[N-1]-arr[0];
    while(left<=right){
        mid = (left + right)/2;
        if(isAns(arr,N,C,mid)){
            ans = mid;
            left = mid+1;
        }
        else right = mid-1;
    }
    cout << ans;
    return 0;
}

选做题


P2118 [NOIP2014 普及组] 比例简化

因为数据很小,暴力搜索即可

#include<iostream>
#include<algorithm>
using namespace std;

int main()
{
    int A, B, L, Ap, Bp;
    cin >> A >> B >> L;
    Ap=L ; Bp=1;
    for(int a=L ; a>0 ; a--)
        for(int b=1 ; b<=L ; b++)
            if(__gcd(a,b)==1 && a*B-A*b>=0 && Ap*b-a*Bp>=0){
                Ap=a ; Bp=b;
            }
    cout << Ap << " " << Bp;
    return 0;
}

P1024 [NOIP2001 提高组] 一元三次方程求解

因为数据很小,精确到小数点后两位,每次加0.01,暴力求解

#include<iostream>
#include<iomanip>
#include<cmath>
using namespace std;
#define eps 1e-6

double a, b, c, d;
double fun(double r)
{
    return a*r*r*r + b*r*r + c*r + d;
}
int main()
{
    cin >> a >> b >> c >> d;
    for(double i=-100 ; i<=100 ; )
    {
        if(fabs(fun(i)) < eps)
        {
            cout << fixed << setprecision(2) << i << " ";
            i += 1;
        }
        else
            i+=0.01;
    }
    return 0;
}

P1873 [COCI2011-2012#5] EKO / 砍树

与必做第三第四题神似,不妨二分查找

时间复杂度 O ( N ∗ l o g N ) O(N*logN) O(NlogN)

代码如下(勉强没超时):

#include<iostream>
using namespace std;

int arr[1000011];
int main()
{
    // freopen("D:\\test.txt", "r", stdin);
    int N,M;
    cin>>N>>M;
    for(int i=0 ; i<N ; i++)
        cin>>arr[i];
    int lef=1, rig=0x3fffffff, ans;
    while(lef<=rig){
        long long len=0; // 要开long long不然len会爆
        int mid=lef+(rig-lef)/2;
        for(int i=0 ; i<N ; i++)
            if(arr[i]>mid)
                len += arr[i]-mid;
        if(len<M) 
            rig = mid-1;
        else{
            ans = mid;
            lef = mid+1;
        }
    }
    cout << ans;
    return 0;
}

P3382 【模板】三分

题目大意:找一个类抛物线函数的最值点

分析:

  1. 首先,要求一个N次函数的值,那么我们首先要写一个函数。
  2. 其次,要找到这个根,可以求导后二分查找,但是导数不好求。可以直接用三分查找。

代码如下:

#include<iostream>
using namespace std;
#define eps 1e-6

double coef[20]; // 系数数组
int N;
double func(double r) // 函数f(x)
{
    double res=0;
    for(int i=0 ; i<=N ; i++){
        double val=1;
        for(int j=N-i ; j>0 ; j--)
            val *= r;
        val *= coef[i];
        res += val;
    }
    return res;
}
double ts(double lef, double rig) // Ternary Search
{
    double l = lef*2/3 + rig/3;
    double r = lef/3 + rig*2/3;
    if(rig-lef<eps) return lef;
    else if(func(l)>func(r)) return ts(lef, r-eps);
    else if(func(l)<func(r)) return ts(l+eps, rig);
}
int main()
{
    // freopen("D:\\test.txt", "r", stdin);
    double lef, rig;
    cin >> N >> lef >> rig;
    for(int i=0 ; i<=N ; i++)
        cin >> coef[i];
    cout << ts(lef, rig);
    return 0;
}

P2678 [NOIP2015 提高组] 跳石头

题目大意:在N个石头中移去至多M个,使剩下来的每对相邻的石头之间的距离最大

分析:和必做第四题很像,先二分查找到一个解,然后把每个点都扫一遍判断解是否合法。如果合法,记录并在更大的范围内尝试寻找更大的解;如果不合法,在更小的范围内搜索。

判断函数:

int arr[50005],L,N,M;
bool islegal(int d)
{
    int i=0, j=1, m=M; // i为当前位置,j为目标位置
    while(j<=N+1){
        if(arr[j]-arr[i] < d) --m; 
            // 如果i和j之间的距离比d还小,必须搬走j
        else i=j;
        ++j;
    }
    if(m<0) return false;
    else return true;
}

问题:因为我们每次搬走的都是 j j j,要遍历所有石头的话 j j j 必须从 1 1 1 扫到 N + 1 N+1 N+1 。但是由题意第 0 0 0 个石头和第 N + 1 N+1 N+1 个石头是不能搬走的,如果是第 N ( 如果它没被搬走的话 ) N(如果它没被搬走的话) N(如果它没被搬走的话) 个石头和第 N + 1 N+1 N+1 个石头之间的距离不合法,要搬走第 N + 1 N+1 N+1 个石头吗?

事实上可以这么理解:当 j = N + 1 j=N+1 j=N+1 时,假设 i = N i=N i=N ,那么 i i i 前所有的石头间距都是合法的,如果第 N N N 个石头和第 N + 1 N+1 N+1 个石头之间的距离不合法,我们可以理解成搬走了第 N N N 个石头,这样在 i i i 前面的那个石头,假设是 N − 1 N-1 N1 ,和第 N + 1 N+1 N+1 个石头的距离一定合法。故此算法从逻辑上来说是合理的。

完整代码如下:

#include<iostream>
using namespace std;

int arr[50005],L,N,M;
bool islegal(int d)
{
    int i=0, j=1, m=M; // i为当前位置,j为目标位置
    while(j<=N+1){
        if(arr[j]-arr[i] < d) --m; // 如果存在比d更小的间距,跳过它
        else i=j;
        ++j;
    }
    if(m<0) return false;
    else return true;
}
int main()
{
    // freopen("D:\\test.txt", "r", stdin);
    cin >>L>>N>>M;
    for(int i=1 ; i<=N ; i++)
        cin >> arr[i];
    arr[N+1] = L;
    int lef=1, rig=L, ans, mid;
    while(lef<=rig){
        mid = lef + (rig-lef)/2;
        if(islegal(mid)){
            ans = mid;
            lef = mid+1;
        }
        else rig = mid-1;
    }
    cout << ans;
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值