第四题 组牌
【问题描述】
现在你有一副牌,其中总共有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两题的题解了,欢迎大家讨论和指正·······