有趣的算法题

文章详细解析了四个编程题目:合并石头的最小力气算法,使用优先队列优化合并过程;寻找回文质数的方法,以及处理大小写和单词匹配的技巧;统计单词个数时的处理策略;最后是解决跳楼梯问题和涉及滑动窗口的最小刺痛值和计算。
摘要由CSDN通过智能技术生成

例题一:甄总搬石头

题目描述:

        现在草地上有n堆石头,甄总想要合并这n堆石头成为1堆,但是他每次能力有限,所以只能一次合并2堆石头至1堆。

        现在已知第i堆石头有ai块,假设甄总要合并第i堆和第j堆石头,则需要花费ai+aj的力气。

        给出n堆石头每堆石头的个数,求出甄总要合并n堆成1堆石头一共需要多少力气。

输入描述:

第1行输入一个整数n,代表一共有n堆石头。

第2行输入n个整数ai,表示第i堆有ai块石头。

输出描述:

输出一行整数,表示一共需要多少力气。

【示例1】

输入:

3
1 2 3

输出:

9

【算法解析】

这里要求的是最小的力气,所以我们得先根据石堆的石头个数进行排序,找到两个最小的石堆(即a[1]和a[2])进行合并,然后我们再将两堆石堆合起来的石头个数存储到a[2]中,再将a[1]赋值为int类型最大的数,让之后排序的时候不再对它进行排序。

代码1:(这段代码可以实现,但是可能会因为算法复杂度太大了超时,只能过得了42.86%的数据)

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
long long q[N],n;
bool cmp(int a,int b){  //升序(sort函数默认升序,所以这里我们需要写个bool函数来实现升序排列) 
	return a<b;  
}
int main(){
	cin>>n;  
	int sum=0,j=n;    //j存储实时变化的石堆 
	for(int i=1;i<=n;i++){   //下标从1开始对数组进行存储(也可以从0开始,个人习惯问题) 
		cin>>q[i]; 
	} 
 	while(j>1){   //三堆石堆合起来最后只有一堆石堆 而我们要进行的操作只有两次,所以这里j>1 
	 	sort(q+1,q+n+1,cmp);   //对石堆石头个数进行排序 
		sum+=q[2]+q[1];       //每次取前面两个最小的石堆进行合并,并记录下合并他们所需要的力气 
		q[2]+=q[1];          //将合并后的石堆存储在q[2]里 
		q[1]=INT_MAX;       // 将 a[0] 赋值为一个较大的数,确保不会再次被选中 
		j--;               //石堆数量减一 
	}
	cout<<sum<<endl;      

	return 0;
}

代码2:运用队列的方法进行操作

【算法思想】 :

  1. 使用 priority_queue(优先队列)数据结构来代替手动排序。priority_queue 可以自动维护堆的性质,将最小的元素放在队首。

  2. 将所有石堆的石头个数存储在 priority_queue 中,并按升序排列。

  3. 依次弹出 priority_queue 中的最小两个元素(即石堆),将它们合并并记录下合并所需要的力气。

  4. 将合并后的石堆重新插入 priority_queue 中,直到只剩下一堆石堆为止。

#include<bits/stdc++.h>
using namespace std;

int main() {
    long long n;
    cin >> n;
  
    priority_queue<long long, vector<long long>, greater<long long>> q; // 使用优先队列实现最小堆
  
    for (int i = 0; i < n; i++) {
        long long stones;
        cin >> stones;
        q.push(stones); // 将石堆的石头个数插入优先队列
    }
  
    long long sum = 0;
  
    while (q.size() > 1) { // 当优先队列中还剩下多于一堆石堆时
        long long a = q.top(); q.pop(); // 弹出最小的石堆
        long long b = q.top(); q.pop(); // 弹出次小的石堆
        sum += a + b; // 累加力气
        q.push(a + b); // 合并后的石堆重新插入优先队列
    }
  
    cout << sum << endl;
  
    return 0;
}

例题二:回文质数 Prime Palindromes

 题目描述:

        因为 151151 既是一个质数又是一个回文数(从左到右和从右到左是看一样的),所以 151151 是回文质数。写一个程序来找出范围 [a,b](5≤a<b≤100,000,000)(一亿)间的所有回文质数。

输入格式:

第一行输入两个正整数 a 和 b。

输出格式

输出一个回文质数的列表,一行一个。

输入 :

5 500

输出 :

5
7
11
101
131
151
181
191
313
353
373
383

说明/提示

Hint 1: Generate the palindromes and see if they are prime.

提示 1: 找出所有的回文数再判断它们是不是质数(素数).

Hint 2: Generate palindromes by combining digits properly. You might need more than one of the loops like below.

提示 2: 要产生正确的回文数,你可能需要几个像下面这样的循环。

题目翻译来自NOCOW。

USACO Training Section 1.5

产生长度为 55 的回文数:

for (d1 = 1; d1 <= 9; d1+=2) {    // 只有奇数才会是素数
     for (d2 = 0; d2 <= 9; d2++) {
         for (d3 = 0; d3 <= 9; d3++) {
           palindrome = 10000*d1 + 1000*d2 +100*d3 + 10*d2 + d1;//(处理回文数...)
         }
     }
 }

【算法解析】:

  1. 暴力枚举[l,r]的每一个,会超时,ac不了

  2. 因为判断回文快,回文数也少,所以先判断回文在判断质数,还是超市吧

  3. 有一个结论:有偶数位的回文数(除了11)必然不是质数

  4. 正偶数(除了2)都不可能是质数,这下次快了很多

  5. 大于9999999的都不可能是回文质数,有缩小可一些范围

代码实现:

#include<bits/stdc++.h>
using namespace std;
bool huiwen(int n){//判断是否为回文数 
	int ans=0,m=n;
	while(n){
		ans=ans*10+n%10;
		n/=10;
	}
	if(ans==m) return true;
	return false;
}
bool weishu(int i){  //判断位数,偶数回文数不可能是质数(除了11) 
	if(i>=10 && i<100 && i!=11 || i>=1000 && i<10000||(i>=1000&&i<=9999)||(i>=100000&&i<=999999)) return false;
	return true;
}
bool isprime(int n){ //判断是否为质数 
	if(n==1) return false;
	for(int i=2;i<=sqrt(n);i++){
		if(n%i==0) return false;
	}
	return true;
} 
int main(){
	int n,m;
	scanf("%d %d",&n,&m);
	if(n==2) printf("2\n"); 
	if(n%2==0) n++; // 偶数都不可能是质数 
	m = min(9999999,m);//再大的数都不可能是回文质数
	for(int i=n;i<=m;i+=2){ //遍历奇数不遍历偶数节省时间 
		 if(!weishu(i)) continue;
		 if(!huiwen(i)) continue;
		 if(!isprime(i)) continue;
 		cout<<i<<endl;
		
	}
	return 0;
} 

题目三:统计单词个数

题目描述

        一般的文本编辑器都有查找单词的功能,该功能可以快速定位特定单词在文章中的位置,有的还能统计出特定单词在文章中出现的次数。

        现在,请你编程实现这一功能,具体要求是:给定一个单词,请你输出它在给定的文章中出现的次数和第一次出现的位置。注意:匹配单词时,不区分大小写,但要求完全匹配,即给定单词必须与文章中的某一独立单词在不区分大小写的情况下完全相同(参见样例 1),如果给定单词仅是文章中某一单词的一部分则不算匹配(参见样例 2)。

输入格式

共 2 行。

第 1 行为一个字符串,其中只含字母,表示给定单词;

第 2 行为一个字符串,其中只可能包含字母和空格,表示给定的文章。

输出格式

一行,如果在文章中找到给定单词则输出两个整数,两个整数之间用一个空格隔开,分别是单词在文章中出现的次数和第一次出现的位置(即在文章中第一次出现时,单词首字母在文章中的位置,位置从 00 开始);如果单词在文章中没有出现,则直接输出一个整数 −1−1。

样例一:

输入 

To
to be or not to be is a question

输出

2 0

输入 

to
Did the Ottoman Empire lose its power at that time

输出 

-1

说明/提示

数据范围

1≤ 第一行单词长度 ≤10。

1≤文章长度 ≤106。

算法思想:

  1. 大小写问题:我们查找单词是不管它的大小写的,所以开始就先对字符串进行处理,要么把他们转化为小写,要么都转化为大写(这里代码是全都转成小写的)
  2. 带空格输入:我们这里用的是c++中的string 类型,需要带空格输入的时候不能直接cin,这时候我们用getline(cin,a)来实现带空格的输入
  3. 单词包含问题:这里想找到单词出现的次数,要注意一个单词里面包含了要查找的单词的情况,这种情况是不符合题目的,所以不能直接find,于是我们对a和b做个处理在其前后都加个空格,这时再去find就不会遇到包含的情况啦

代码实现:

#include <bits/stdc++.h>
using namespace std;
int main(){
    //定义两个字符串
    string a;
    string b;
    //用string库,调用getline, 直接读入一整行
    getline(cin,a);
    getline(cin,b);
    //转换大小写,可以都转换为大写,或者小写
    for (int i=0;i<a.length();++i){
        a[i]=tolower(a[i]);
    }
    for (int i=0;i<b.length();++i){
        b[i]=tolower(b[i]);
    }
    //因为连起来的不算,所以要在前后加几个空格,一定要是同样多的,同量减同量,等于同量
    a=' '+a+' ';
    b=' '+b+' ';
    //先看看会不会找不到,用a.find()和string::npos
    if (b.find(a)==string::npos){
        cout<<-1<<endl;
    }
    //如果找得到
    else {
        int alpha=b.find(a);
        int beta=b.find(a),s=0;//计数器初始化为0
        while (beta!=string::npos){
            ++s;//计数器
            beta=b.find(a,beta+1);
        }
        cout<<s<<" "<<alpha<<endl;//输出第一个和总共有几个
    }
    //函数返回值为0,结束整个程序
    return 0;
}

例题四:跳楼梯

 题目描述:

一个楼梯共有 n级台阶,每次可以走一级或者两级,问从第 00 级台阶走到第 n�级台阶一共有多少种方案。

输入格式

共一行,包含一个整数 n。

输出格式

共一行,包含一个整数,表示方案数。

数据范围

1≤n≤151

输入样例:

5

输出样例:

8

代码实现:

#include<bits/stdc++.h>
using namespace std;
int cmt=0;
int n;
void f(int k){
	if(k==n) cmt++;
	else if(k<n){
		f(k+1);
		f(k+2);
	}
}
int main(){
	cin>>n;
	f(0);
	cout<<cmt<<endl;
	
	return 0;
}

例题五:爱与愁的心痛

题目背景

(本道题目隐藏了两首歌名,找找看哪~~~)

《爱与愁的故事第一弹·heartache》第一章。

《我为歌狂》当中伍思凯神曲《舞月光》居然没赢给萨顶顶,爱与愁大神心痛啊~~~而且最近还有一些令人伤心的事情,都让人心痛(最近真的很烦哈)……

题目描述

最近有 n 个不爽的事,每句话都有一个正整数刺痛值(心理承受力极差)。爱与愁大神想知道连续 m 个刺痛值的和的最小值是多少,但是由于业务繁忙,爱与愁大神只好请你编个程序告诉他。

输入格式

第一行有两个用空格隔开的整数,分别代表 n 和 m。

第 2行到第(n+1) 行,每行一个整数,第 (i+1) 行的整数a[i] 代表第 i 件事的刺痛值 a[i]。

输出格式

输出一行一个整数,表示连续 m 个刺痛值的和的最小值是多少。

输入输出样例

输入 

8 3
1
4
7
3
1
2
4
3

输出 

6

题目解析:

我们可以使用滑动窗口的方法来解决这个问题。首先,我们将窗口设置为长度为 m 的初始窗口,计算窗口内刺痛值的和。然后,我们向右滑动窗口,每次移动一格,重新计算窗口内刺痛值的和,并与之前的最小值进行比较,更新最小值。

具体的算法如下:

  1. 读取输入的 n 和 m。
  2. 读取刺痛值列表 a。
  3. 初始化窗口内刺痛值的和为 sum(a[0:m])。
  4. 初始化最小刺痛值和为当前窗口内刺痛值的和。
  5. 从 i = m 到 i = n-1 迭代:
    • 移除 a[i-m],更新窗口内刺痛值的和为 sum(a[i-m+1:i+1])。
    • 如果当前窗口内刺痛值的和小于最小刺痛值和,更新最小刺痛值和为当前窗口内刺痛值的和。
  6. 输出最小刺痛值和。

根据题目描述,我们可以使用上述算法来求解最小刺痛值和。在给定的输入样例中,最小刺痛值和为 6。

代码实现:

#include<bits/stdc++.h>
using namespace std;
int main(){
	int n,m;
	cin>>n>>m;
	vector<int> a(n);
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	int sum=0;
	for(int i=0;i<m;i++){
		sum+=a[i];
	}
	int minsum=sum;
	for(int i=m;i<n;i++){
		sum+=a[i]-a[i-m];//进行窗口滑动 
		minsum=min(minsum,sum);
	}
	cout<<minsum<<emdl;
	
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值