【多路归并专题】&【蓝桥杯备考训练】:鱼塘钓鱼、谦虚数字、序列、技能升级【已更新完成】

文章探讨了四道IT竞赛题目:通过动态规划解决鱼塘钓鱼问题,质因数分析计算谦虚数字,序列和优化算法,以及技能升级策略的计算。
摘要由CSDN通过智能技术生成

目录

1、鱼塘钓鱼(《信息学奥赛一本通》)

2、谦虚数字(usaco training 3.1)

3、序列(《算法竞赛进阶指南》)

4、技能升级(第十三届蓝桥杯 省赛 C++ C组 & Java 研究生组 & Python B组/研究生组)


1、鱼塘钓鱼(《信息学奥赛一本通》)

有 N 个鱼塘排成一排,每个鱼塘中有一定数量的鱼,例如:N=5 时,如下表:

鱼塘编号12345
第1分钟能钓到的鱼的数量(1..1000)101420169
每钓鱼1分钟钓鱼数的减少量(1..100)24653
当前鱼塘到下一个相邻鱼塘需要的时间(单位:分钟)3544

即:在第 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;
} 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值