目录
4、技能升级(第十三届蓝桥杯 省赛 C++ C组 & Java 研究生组 & Python B组/研究生组)
1、鱼塘钓鱼(《信息学奥赛一本通》)
有 N 个鱼塘排成一排,每个鱼塘中有一定数量的鱼,例如:N=5 时,如下表:
鱼塘编号 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
第1分钟能钓到的鱼的数量(1..1000) | 10 | 14 | 20 | 16 | 9 |
每钓鱼1分钟钓鱼数的减少量(1..100) | 2 | 4 | 6 | 5 | 3 |
当前鱼塘到下一个相邻鱼塘需要的时间(单位:分钟) | 3 | 5 | 4 | 4 |
即:在第 1 个鱼塘中钓鱼第 1分钟内可钓到 10条鱼,第 2 分钟内只能钓到 8 条鱼,……,第 5 分钟以后再也钓不到鱼了。
从第 1 个鱼塘到第 2个鱼塘需要 3 分钟,从第 2 个鱼塘到第 3 个鱼塘需要 5 分钟,……
给出一个截止时间 T,设计一个钓鱼方案,从第 1个鱼塘出发,希望能钓到最多的鱼。
假设能钓到鱼的数量仅和已钓鱼的次数有关,且每次钓鱼的时间都是整数分钟。
输入格式
共 5 行,分别表示:
第 1 行为 N;
第 2 行为第 1 分钟各个鱼塘能钓到的鱼的数量,每个数据之间用一空格隔开;
第 3 行为每过 1 分钟各个鱼塘钓鱼数的减少量,每个数据之间用一空格隔开;
第 4 行为当前鱼塘到下一个相邻鱼塘需要的时间;
第 5 行为截止时间 T。
输出格式
一个整数(不超过INT_MAX),表示你的方案能钓到的最多的鱼。
数据范围
1≤N≤100
1≤T≤1000
输入样例:
5
10 14 20 16 9
2 4 6 5 3
3 5 4 4
14
输出样例:
76
思路:
一一枚举到每个鱼塘能钓到的鱼,那么,如何枚举?
先用前缀和算出到每个鱼塘所耗费的时间,然后算出目前方案能钓出鱼最多的方案,用一个变量维护最大值,最后输出最大值即可
代码:
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
#define x first
#define y second
const int N=105;
int a[N],b[N],s[N];
int main()
{
int n,T;
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)cin>>b[i];
for(int i=2;i<=n;i++)cin>>s[i];
cin>>T;
for(int i=2;i<=n;i++)s[i]+=s[i-1];//求第一个鱼塘到第i个的时间
//cout<<s[2];
int res=0;
for(int i=1;i<=n;i++)
{
int havetime=T-s[i];
priority_queue<PII,vector<PII>> rivers;
int fish=0;
for(int k=1;k<=i;k++)
{
rivers.push({a[k],k});//鱼塘入堆,开始钓!
}
while(!rivers.empty() && havetime>0)
{
PII t=rivers.top();
rivers.pop();
fish+=t.x;
havetime-=1;
if(t.x-b[t.y]>0)rivers.push({t.x-b[t.y],t.y});
}
//cout<<fish<<endl;
res=max(fish,res);
}
cout<<res;
return 0;
}
2、谦虚数字(usaco training 3.1)
给定一个包含 K个不同质数的集合 S={p1,p2,…,pK}
如果一个数的质因子全部属于集合 S,那么我们就称这个数为谦虚数字。
例如,p1、p1*p2、p1*p1、p1*p2*p3 这些数字都是谦虚数字。
现在给定整数 K和集合 S,请你求出从小到大第 N 个谦虚数字是多少。
注意,我们规定 1 不是谦虚数字。
输入格式
第一行包含两个整数 K 和 N。
第二行包含 K 个质数。
输出格式
输出一个整数,表示第 N 大的谦虚数字。
数据保证,答案在 int 范围内。
数据范围
1≤K≤100
1≤N≤105
输入样例:
4 19
2 3 5 7
输出样例:
27
思路:
用一个数组维护每个元素在归并数组中的位置,每次取最小的谦虚数字,然后将数组中对应的指针向后移一位
代码:
#include<bits/stdc++.h>
using namespace std;
int K,n;
const int N=1e5+5;
int a[N],b[N];//b用来记录丑数指针的位置
int main()
{
cin>>K>>n;
for(int i=0;i<K;i++)
{
cin>>a[i];
}//读入质数集合
sort(a,a+K);
vector<int>res(1,1);
while(res.size()<=n)
{
int t=INT_MAX;
for(int i=0;i<K;i++)t=min(t,res[b[i]]*a[i]);
res.push_back(t);
for(int i=0;i<K;i++)
{
//调整指针
if(res[b[i]]*a[i]==t)b[i]++;
}
}
cout<<res.back();
return 0;
}
3、序列(《算法竞赛进阶指南》)
给定 m 个序列,每个包含 n 个非负整数。
现在我们可以从每个序列中选择一个数字以形成具有 m 个整数的序列。
很明显,我们一共可以得到 n的m次方个这种序列,然后我们可以计算每个序列中的数字之和,并得到 n的m次方个值。
现在请你求出这些序列和之中最小的 n 个值。
输入格式
第一行输入一个整数 T,代表输入中包含测试用例的数量。
接下来输入 T 组测试用例。
对于每组测试用例,第一行输入两个整数 m和 n。
接下在 m 行输入 m 个整数序列,数列中的整数均不超过 10000。
输出格式
对于每组测试用例,均以递增顺序输出最小的 n个序列和,数值之间用空格隔开。
每组输出占一行。
数据范围
0<m≤1000
0<n≤2000
输入样例:
1
2 3
1 2 3
2 2 3
输出样例:
3 3 4
思路:
排序第一行,固定第一行,用一个小顶堆维护最小的和的值,进入循环,把最大值取出来,弹出,接着下一个最大值入堆,最后把最大的n个值给临时数组c,最后c再给到原数组,一直枚举直到m数组,最后a数组中就是前n个最大值
代码:
//二叉堆
#include<bits/stdc++.h>
using namespace std;
int T;
typedef pair<int,int> PII;
#define x first
#define y second
int m,n;
const int N=3000;
int a[N],b[N],c[N];
void merge()
{
priority_queue<PII,vector<PII>,greater<PII>> minheap;
for(int i=1;i<=n;i++)minheap.push({a[1]+b[i],1});
for(int i=1;i<=n;i++)
{
PII t=minheap.top();
minheap.pop();
int s=t.x;
int p=t.y;
c[i]=s;
minheap.push({s-a[p]+a[p+1],p+1});
}
for(int i=1;i<=n;i++)a[i]=c[i];
}
int main()
{
cin>>T;
while(T--)
{
cin>>m>>n;
for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+n+1);
for(int i=0;i<m-1;i++)
{
for(int j=1;j<=n;j++)cin>>b[j];
merge();
}
for(int i=1;i<=n;i++)cout<<a[i]<<" ";
cout<<endl;
}
return 0;
}
4、技能升级(第十三届蓝桥杯 省赛 C++ C组 & Java 研究生组 & Python B组/研究生组)
小蓝最近正在玩一款 RPG 游戏。
他的角色一共有 N 个可以加攻击力的技能。
其中第 i 个技能首次升级可以提升 Ai 点攻击力,以后每次升级增加的点数都会减少 Bi。
⌈Ai/Bi⌉(上取整)次之后,再升级该技能将不会改变攻击力。
现在小蓝可以总计升级 M次技能,他可以任意选择升级的技能和次数。
请你计算小蓝最多可以提高多少点攻击力?
输入格式
输入第一行包含两个整数 N 和 M。
以下 N行每行包含两个整数 Ai和 Bi。
输出格式
输出一行包含一个整数表示答案。
数据范围
对于 40%40% 的评测用例,1≤N,M≤1e3;
对于 60%60% 的评测用例,1≤N≤1e4,1≤M≤1e7;
对于所有评测用例,1≤N≤1e5,1≤M≤2 * 1e9,1≤Ai,Bi≤1e6。
输入样例:
3 6
10 5
9 2
8 1
输出样例:
47
思路:
我们不断的增大期望的每次能升级攻击力的值(设为x),以期望求得最大的x,二分的性质就出来了,如果最大是x,那么大于x的就无法满足题意,小于等于x的肯定能满足题意,最终我们求出来的x表示:能满足加技能次数>=m,并且取的最大的数
每个等差数列中含多少个x我们能用(a[i]-x)/d[i]+1这个公式算出来
求出来x之后我们就可以求和了,遍历一遍每个等差数列的首项,再次利用上面的公式求出项数,然后利用等差数列公式求出该等差数列中大于等于x的元素的合,每个都这样累加,最终算出每个数组的结果累加上。
但现在还不是最终答案,因为x可能有多个,可能会多加一些x,所以说我们在累加的同时记数,最后的结果就是累加的结果减去(cnt-m)*r,就能把多加的r减掉
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+3;
typedef long long LL;
int n,m;
LL a[N],b[N];
bool check(int mid)
{
LL sum=0;
for(int i=1;i<=n;i++)
{
if(a[i]>=mid)sum+=(LL)(a[i]-mid)/b[i]+1;
}
return sum>=m;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&a[i],&b[i]);
}
LL l=0,r=1e6;
while(l<r)
{
LL mid=(l+r+1)>>1;
if(check(mid))l=mid;
else r=mid-1;
}
LL res=0,cnt=0;
for(int i=1;i<=n;i++)
{
if(a[i]>r)
{
LL c=(a[i]-r)/b[i]+1;
LL ed=a[i]+(-1)*(c-1)*b[i];//an = a1+(n-1)d
LL sum=(a[i]+ed)*c/2;
res+=(LL)sum;
cnt+=c;//加了多少次
}
}
cout<<res-(cnt-m)*r;
return 0;
}