校内算法赛部分题解

本文介绍了在算法竞赛中处理组牌问题和避开障碍路径的策略,包括二分法求解组牌最大数量和动态规划求解棋盘路径数。
摘要由CSDN通过智能技术生成

第四题  组牌

【问题描述】           

现在你有一副牌,其中总共有n种牌,每种牌都有ai张,如果n种牌每种都有一张的话则可以凑成一组牌。为了多组几组牌,现在拿来了m张空白牌,你可以选择将这些空白牌转化成任意一种牌。但是,每种牌可转化的上限为bi张,请问你通过转化最多可以获得几组牌?

 【输入格式】  

 输入共 3 行,第一行为两个正整数n,m。

 第二行为n个正整数a1​,a2​,…,an​。

 第三行为n个正整数b1​,b2​,…,bn​。

【输出格式】

 输出一个整数,表示答案。

【样例输入】

 4 5
 1 2 3 4
 5 5 5 5

【样例输出】 

      3

【样例说明】

这5张空白牌中,拿2张写1,拿1张写2,这样每种牌的牌数就变为了3,3,3,4,可以凑出3套牌,剩下2张空白牌不能再帮助小明凑出一套。

【评测用例规模与约定】

 对于40%的数据,保证n≤2000;

 对于100%的数据,保证n ≤ 2 X 10^5; ni,bi ≤ n; m ≤ n^2

【解题思路】

首先此题当我们看到题目时会想到打个暴力,第一思路是想去模拟题目中所描述的过程,选择从大到小遍历可能凑出的牌套数,计算凑出它需要补的牌数以及判断是否会超出能补的牌数

但当我们看到题目中的时间复杂度以及题目中问“请问你通过转化最多可以获得几组牌?”时其实就应该想到贪心或者二分了,不难分析出最多获得的套数一定在最小的ai~最大的(ai+bi)中。

于是我们可以在最小的ai和最大的ai+bi之间找到一个最大的数x,使得在获得x套牌数的情况下,转化张数无论是在独立一种牌还是在总体m张空白牌的情况下,都不能转化张数越界,如果越界则返回false,否则返回true。

所以使用二分法找到x的最大值即为答案。关于二分大家可以看看这篇博客,已经讲的非常详细了

https://www.acwing.com/file_system/file/content/whole/index/content/9064240/?from=app_share

二分说人话就是把答案在区间内逐渐缩小,直至区间内只有答案

【c++代码实现】

#include<iostream>
#include<algorithm>

using namespace std;
 
const int N=2e5+10;
 
int n,m,l,r;
int a[N];
int b[N];
 
bool check(int x){
    int s = 0;
    for (int i = 0; i < n; i++) {
        if (x - a[i] <= b[i]) {
            s += max(x - a[i],0);//统计组成x套牌需要补s张牌
        }
        else return false;//超出了b[i]
    }
    if (s <= m) return true;//没有越界
    return false;
}
 
int main(){
    cin>>n>>m;
    for(int i=0;i<n;i++){
        cin>>a[i];
        l = min(l, a[i]);
    }
    for(int i=0;i<n;i++){
        cin>>b[i];
        r=max(r,a[i]+b[i]);
    }    
    int ans;
    while(l<=r){
        int mid=(l+r)/2;//二分组成的牌套数
         if(check(mid)){
             ans=mid;
             l=mid+1;
         }else{
             r=mid-1;
         }
    }
    cout<<ans<<endl;
    return 0;
}

 附上暴力的做法以供大家参考:(不能通过所有样例,所以可以选择二分进行优化,把时间复杂度控制在O(logn)级别)。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

const int N =2e5+10;
int a[N],b[N];
int n,m;
int sum=0;
vector<int> v;
int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
        v.push_back(a[i]);
    }
    for(int i=0;i<n;i++) cin>>b[i];
    sort(v.begin(),v.end());
    for(int i=n-1;i>=0;i--)//从大到小遍历
    {
        int state=0;//打个标记
        int sum=0;//所有总数都改成v[i]加起来所需要的牌数
        for(int j=0;j<n;j++)
        {
            int need =(v[i]-a[j]);
            if(need>0)
            {
                sum+=need;
            }
            if(b[j]-need<0) 
            {
                state=1;//不能补
                break;
            }
        }
        if(sum>m||(state==1)) continue;
        else 
        {
            cout<<v[i]<<endl;
            break;
        }
    }
    return 0;
}

第五题  聪明的兵

【问题描述】           

昨天通宵玩象棋的小蓝,起床的小蓝发现穿越到兵上了。并且系统下达了任务。

任务:需要小蓝从起点(0,0),到达终点S(sx, sy)

小蓝的行走规则可以向下、或者向右。

但是任务并不是那么轻松完成的!棋盘上有一只马,小蓝不能走到该马所在的点和所有跳跃一步可达的点。

注;这只马非常懒,马的位置是固定不动的,并不是小蓝走一步马走一步。

现在,请问小蓝安全到达终点的路径条数。

 【输入格式】  

 一行四个整数,分别表示终点坐标和马的坐标

【输出格式】

 一个整数,表示所有的路径条数。

【样例输入】

 6 6 3 3

【样例输出】 

  6

【评测用例规模与约定】

 对于100%的数据,1≤sx,sy≤20, 0≤ 马的坐标 ≤20。

【解题思路】

本题一眼dp,好在这个dp比较简单,最后还是ac了,让我们统计路径,看起来是个搜索题,可以用bfs求解,十分像“走迷宫”那个板题,我们可以把马的控制点标记为不可到达点,饶过他们,但是搜索容易超时,而且bfs写起来比较麻烦,还是老老实实dp吧。

dp其实也就是递推,我们可以在每个坐标上记录能走的路径条数,然后不难找出递推公式。

下面我用闫式dp分析法给大家分析一下哈哈:

 因为只能往下或者往右走,所以走到(i,j)位置必然是从上方或者左方走过来的,(i,j)点的路径也就是它上面和左边的路径之和

还有一点要注意的便是本题的限制条件为马的控制点,只要令控制点的dp[i][j]=0即可,即这个点上无路径。注意到了这点我们就能正常写代码了hh.

下面给出代码供大家参考:

【c++代码实现】

#include <iostream>
#include <cstring>

using namespace std;

const int N = 100;

long long dp[N][N];
bool state[N][N];

int main()
{
    int n, m, x, y;
    cin >> n >> m >> x >> y;

    memset(dp, 0, sizeof(dp));
    memset(state, false, sizeof(state));

    state[x][y] = true;
    int dx[] = {-2, -2, -1, -1, 1, 1, 2, 2};
    int dy[] = {-1, 1, -2, 2, -2, 2, -1, 1};

    for (int k = 0; k < 8; ++k) {
        int nx = x + dx[k];
        int ny = y + dy[k];
        if (nx >= 0 && nx <= n && ny >= 0 && ny <= m) {
            state[nx][ny] = true;
        }
    }
     //true表示不能走,有障碍,false表示无障碍可以走
    for (int i = 0; i <= m; ++i) {
        if (!state[0][i])
            dp[0][i] = 1;
        else
            break;
    }
     //对第一行和第一列特判
    for (int i = 0; i <= n; ++i) {
        if (!state[i][0])
            dp[i][0] = 1;
        else
            break;
    }

    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            if (!state[i][j])
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
        }
    }

    cout << dp[n][m] << endl;

    return 0;
}

以上就是本次算法赛4,5两题的题解了,欢迎大家讨论和指正·······

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值