BuaaCoding 026-050 Problems and Solutions

PART I 水题

026 jhljx去太空 浮点数计算
027 Collatz Conjecture 奇偶归一猜想 模拟证明 + 记忆化加速
028 pluperfect digital invariant 水仙花数
029 A+Bi Problem 虚数相加
030 水水的的分数相加
031 神奇的桌子 线性检查
032 cool_breeze的袜子 配对计算
034 判断三角形
036 jhljx水水的签到题
037 水水的比较大小 浮点数比较大小
044 jhljx学函数
048 初见杀
049 盗墓笔记之怒海潜沙(最小公倍数)
045 大神杀 (浮点数回文判断)
这题随水却有注意点,题意,给出一个浮点数判断是否为回文数,12.21是,1.221不是。数据范围保证[0, 100000)小数位数不超过5位,精度1e-6。这个题已开始用字符串判断居然过不了,然后转而用double。其他注意点见代码中注释

#include <cstdio>
#define eps 1e-6
using namespace std;
//一般float准确的数位是6位 范围 -3.40E+38 ~ +3.40E+38
//double准确的数位是15位 范围 -1.79E+308 ~ +1.79E+308
bool judge_ph(double a) {
	a += eps;   //这句话不可少  考虑到计算机存储误差1.5可能存成1.499999的情况 因为类型转化的时候直接截断
	for (int base = 1; base <= 10000; base *= 10) 
		if (((long long) a / base) % 10 != (long long)(a * base * 10) % 10) 
			return false;
	return true;
}
int main(int argc, char *argv[]) {
	double a;
	while(scanf("%lf", &a) != EOF) {
		if (judge_ph(a))	printf("Yes\n");
		else	printf("No\n");
	}
	return 0;
}

047 不要不要数列求和 (离线 负数求余 ((a - b)%MOD + MOD) % MOD)

PART II 算法题

037 简单优先队列 (贪心 优先队列)

题目

最近,Nova君遇到了一件非常棘手的问题。他需要整理非常多的解题报告。每份解题报告的题目数量是不定的。Nova君每次需要将两份报告的题目解析合成到一份里。假设两份报告的题解数分别为a和b,那么合成这两份报告消耗Nova君a+b的hp值。现在有n份报告,题解数分别为a0,a1,a2,an-1,请问Nova最少消耗多少hp?

分析

最优二叉树,贪心算法

补充:c++优先队列的使用

//头文件
#inlude <queue>
//方法
empty()
pop()    删除第一个元素
push()    加入一个元素
size()    返回优先队列中拥有的元素个数
top()      返回优先队列中有最高优先级的元素
//优先级
priority_queue<int,vector<int>, greater<int> > q //最小优先队列
priority_queue<int> q //最大优先队列

struct node {     
  int x, y;     
  friend bool operator < (node a, node b)     
  {         
    return a.x > b.x;    //结构体中,x小的优先级高     
  }
};

解答

#include <cstdio>
#include <queue>
using namespace std;
int main(int argc, char *argv[]) {
	int n;
	while(scanf("%d", &n) != EOF) {
		priority_queue<int,vector<int>, greater<int> > q;
		for (int i = 0; i < n; i++) {
			int tmp;
			scanf("%d", &tmp);
			q.push(tmp);
		}
		int res = 0;
		while(q.size() > 1) {
			int merge = q.top();
			q.pop();
			merge += q.top();
			q.pop();
			res += merge;
			q.push(merge);
		}
		printf("%d\n", res);
	}
}

039 简单的二分法

题目

给你一非降序数列,以及一组查询,查询某一特定元素是否存在于数列之中,如果存在,则输出该元素首次出现的位置,否则输出"error"。

分析

二分查找,细节是相等时候的处理。

解答

#include <cstdio>
#define MAX 250005
using namespace std;
int a[MAX];
int bin_search(int a[], int l, int r, int tar) {
	while(l < r) {
		int m = (l + r) >> 1;
		if (a[m] < tar) {
			l = m + 1;
		} else if (a[m] > tar) {
			r = m - 1;
		} else {
			r = m;
		}
	}
	if (a[l] == tar) {
		return l;
	} else {
		return -1;
	}
}
int main(int argc, char *argv[]) {
	int n, m;
	while(scanf("%d%d", &n, &m) !=EOF) {
		for (int i = 0; i < n; i++) {
			scanf("%d", &a[i]);
		}
		for (int i = 0; i < m;i++) {
			int tar;
			scanf("%d", &tar);
			int idx = bin_search(a, 0, n - 1, tar);
			if (idx == -1) {
				printf("error\n");
			} else {
				printf("%d\n", idx + 1);
			}
		}
	}
}

040 零崎的战争(最近点对)

经典问题:详见我的另一篇博文

041 大整数相乘

题目

计算A*B

输入

第一个数为数据组数n

接下来n行,每行2个整数a,b

输出

对于每组数据,输出一行,a*b的值

分析

这题绝对是个坑,一开始无脑贴个大数模板,居然TLE,然后以为是必须要用分治策略,但是我又好奇为啥别人的代码比我的短。。
将信将疑之际,在牛客网找到了这个题,人家那个AC的代码连不仅是O(n2),还是一位一位算的,然而交上去居然过了。。。
但是牛客网这个答案不能处理负数,不能处理负数都过了,真的水。。。。
那为什么模板居然TLE啊啊啊啊啊啊

解答

#include <cstdio>
#include <string>
#include <iostream>
#include <cstring>
#define L 100005
using namespace std;

string mult(string a, string b) {
	int signa = 1, signb = 1;
	if (a[0] == '-') {
		signa = -1;
	}
	if (a[0] == '+' || a[0] == '-') {
		a = a.substr(1);
	}
	if (b[0] == '-') {
		signb = -1;
	}
	if (b[0] == '+' || b[0] == '-') {
		b = b.substr(1);
	}
	int signres = signa * signb;
	int diga[L];
	int digb[L];
	int res[2*L + 5];
	memset(res, 0, sizeof(res));
	//反向存储
	for (int i = a.length() - 1; i >= 0; i--) {
		diga[a.length() - i - 1] = a[i] - '0';
	}
	for (int i = b.length() - 1; i >= 0; i--) {
		digb[b.length() - i - 1] = b[i] - '0';
	}
	for (int i = 0; i < a.length(); i++) {
		if (diga[i] == 0) {
			continue;
		}
		for (int j = 0; j < b.length(); j++) {
			res[i + j] += diga[i] * digb[j];
		}
	}
	for (int i = 0; i < a.length() + b.length(); i++) {
		res[i + 1] += res[i] / 10;
		res[i] %= 10;
	}
	int i = a.length() + b.length() - 1;	
	while(i >= 0 && res[i] == 0) {
		i--;
	}
	string s = "";
	if (signres == -1) {
		s += '-';
	}
	while(i >= 0) {
		s += res[i] + '0';
		i--;
	}
	if (s == "-" || s == "") {
		s = "0";
	}
	return s;
}

int main(){
	int n;
	scanf("%d", &n);
	getchar();
	string a, b;
	while(cin >> a >> b) {
		cout << mult(a, b) << endl;
	}
}

042 不要不要丧心病狂(拐点维护的优雅写法)

题目

jhljx又决定来出题了,上次上机以后有人说他是黑心学长,,噗。。。
肿么可以这样。。
松辰学妹对jhljx说不要不要丧心病狂。。jhljx说我给你出一道题,你要是答对了,你就赢了。。
松辰学妹说这还不简单啊。。
于是jhljx给了松辰学妹一个长度为n的数列A1,A2,A3……An。他说你能找出这个序列中a[i]-a[j]的最大值吗(保证i<j)?
松辰学妹说用数组啊。。jhljx说,没学数组,不准用。。用了你就跪了。。 松辰学妹说好吧。。这可怎么办呢?
输入
输入多组数据。
每组数据两行,第一行是一个正整数n(2<=n<=1000000)。第二行是n个数,每个数之间用空格隔开。保证这些数在int范围内。
输出
输出满足条件的最大值。

分析

很快发现其实是找最大下降子串,关键是怎么写得优雅。
我一开始写的代码一直在维护拐点的逻辑,十分丑陋,还老是WA。。。
优雅的写法是发现出现拐弯时修改一下左值即可。
另外关于res的初始值设定,我一般比较虚初始值,怕设小了,但是其实两个整数的差的最大值是INT_MIN-INT_MAX,所以其实res设成int还是可能爆的。。。

解答

#include <cstdio>
#define max(a, b) (a>b?a:b)
using namespace std;
int main(int argc, char *argv[]) {
	int n;
	while(scanf("%d", &n) != EOF) {
		int a, b, res = -10000000;
		scanf("%d", &a);
		for(int i = 1; i < n; i++) {
			scanf("%d", &b);
			res = max(res, a - b);
			if(a - b < 0) {
				a = b;
			}
		}
		printf("%d\n", res);
	}
}

043 且听风吟之呆逼二三事(博弈论)

题目

wzc是Tera里且听风吟公会的会长,本着为会员着想的心理,努力发展公会,公会人数也是日益壮大,慢慢的就加入了各大公会战,野外打狗的行列。
但是公会的某个妹子突然被呆比抢走并且已经发展到线下了(hhhh
广大会员和会长怎么能忍,随着呆比的发展,渐渐不见他上线,所以大家偷偷决定将他驱逐公会,但是会长wzc是个民主的会长(权限狗),他决定打开公会仓库,让呆比拿走一些金子作为他两个月来的贡献,但是wzc比较大方(抠门),定了下面的规矩:
wzc和呆比轮流取金子,wzc先取,第1次可以取任意多的金币,但不能全部取完.以后每次取的金子不能超过上次取的2倍,如果最后是wzc取完那么呆比取到的金子就要还回来,反之呆比可以拿走取到的金子。假设呆比和wzc都竭尽全力想要赢。
wzc沉迷于公会战和各种事物不能自拔所以需要你来判断呆比能不能全身而退。
输入
输入有多组
.每组1行只有一个数n,代表仓库里的金子数,wzc很穷(有钱)所以2<=n<2^31。
输出
如果wzc取完 则输出"Oh,yes!" 如果呆比取完 则输出"Oh,holly shit!"(我英语学的不好请原谅)

分析

本质是一个fibonacci博弈
博弈论的系统梳理,详见我的另一篇博文

解答

#include <iostream>
#define END 46
using namespace std;
int fib[END];
//fib第46个值 开始溢出
int main(int argc, char *argv[]) {
	fib[0] = 1;
	fib[1] = 1;
	for (int i = 2; i < END; i++) {
		fib[i] = fib[i - 1] + fib[i-2];
	}
	int n;
	while(scanf("%d", &n) != EOF) {
		int i;
		for (i = 0; i < END; i++) {
			if (fib[i] == n) {
				break;
			}
		}
		if (i == END) {
			printf("Oh,yes!\n");
		} else {
			printf("Oh,holly shit!\n");
		}
	}
	return 0;
}

046 李逍遥的仙剑客栈(贪心)

题目

给你一个数n表示有多少个桌子,接下来给你一段整数序列表示每个桌上需要的酒量(假设这些桌子在一条直线上,且每个桌子之间的距离都是1),正数表示多放了 几瓶酒,负数表示应该放多少瓶酒。请你帮Arthur算一下他提着酒走的最短路程是多少。对了,Arthur体力太渣,一次只能拿一瓶酒。
输入
第一行一个数T表示有T组数据。
接下来T组数据,每组数据有2行。
第一行一个数n(1<=n<=1000),表示桌子数量,
接下来第二行有一段数列Ai(|Ai|<=1000),表示每个桌子上应该放的酒。保证数列总和为0
输出
对于每组测试数据,输出一个数,表示Arthur拿着酒走的最少总路程。

分析

贪心的问题从来都是这样,想起来难,写起来容易。。
第一个观察,n和数字的大小都是1000以内,这就说明,不能简单地说n是1000就去尝试一个O(n2)的解法,这里显然是两个数字范围相乘不会爆,只是说用整型不会溢出。
对于一个正数位置,把它往最近的负数上方不亏,一样近的情况是无所谓的,因为酒瓶之间无差异,人不带酒走路无代价。所以一样近的两个位置选取不会影响后面的选择。正向和反向也是没有差异的,所以带着负数走路相当于反相携带酒瓶。
这看起来像一道经典题,还是记住吧。贪心问题想通过证明推出来需要的数学功底绝非一日之功。。。。

解答

#include <cstdio>
using namespace std;
int main(int argc, char *argv[]) {
	int T;
	scanf("%d", &T);
	while(T--) {
		int n;
		scanf("%d", &n);
		int res = 0;
		int count = 0;
		while(n--) {
			int t;
			scanf("%d", &t);
			res += (count > 0? count: -count);
			count += t;
		}
		printf("%d\n", res);
	}
} 

050 零崎的补番计划Ⅰ (中位数法)

题目

给定n个不一样的数,求第k大的数。

分析

排序O(n logn)
但是可以有两个更快的算法时间复杂度为O(n)

解答

排序秒过 代码也很短 但是我们追求卓越。。。
详见我的另一篇博文

PART III 数学题

033 感受学长的爱意吧 (解方程组)

题目

奇怪的人类觉得最近在3楼吃香锅的时候,等待的人生真是寂寞如雪。
于是他拿了1副半的筷子开始摆弄,还脑洞大开地以桌面中心为原点建了个xOy的坐标轴=、= 然后,他开始算交点的坐标了……

分析

两两联立解方程组,注意平行情况的特殊判断。采用方程表示法可以不用讨论斜率不存在的情况,简单了不少,随后去重和排序暴力法即可,因为最多三个解。
小细节:可能出现-0.00的情况,这个加个特判吧。
解方程组写得还行,但是后面去重和排序写得比较丑。

解答

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
using namespace std;
double * cal_two(int a1, int b1, int c1, int a2, int b2, int c2) {
	double * res = NULL;
	if ((a1 == 0 && b1 == 0) || (a2 == 0 && b2 == 0)) {
		//有直线不存在
		return res;
	} else if (a1 * b2 == a2 * b1) {
		//直线平行
		return res;
	} else {
		//一般情况
		res = (double*)malloc(sizeof(double)*2);
		res[0] = (double)(b1*c2 - b2*c1) / (a1*b2 - a2*b1);
		res[1] = (double)(a2*c1 - a1*c2) / (a1*b2 - a2*b1);
	}
	return res;
}
int main(int argc, char *argv[]) {
	int a[9];
	while(scanf("%d%d%d%d%d%d%d%d%d", &a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &a[6], &a[7], &a[8])!=EOF) {
		// 求解
		double ** reses = (double**)malloc(sizeof(double*)*3);
		reses[0] = cal_two(a[0], a[1], a[2], a[3], a[4], a[5]);
		reses[1] = cal_two(a[0], a[1], a[2], a[6], a[7], a[8]);
		reses[2] = cal_two(a[3], a[4], a[5], a[6], a[7], a[8]);
		int no_dup[3] = {-1, -1, -1};
		int cnt = 0;
		// 去重
		for (int i = 0; i < 3; i++) {
			if (reses[i] != NULL) {
				int j;
				for (j = 0; j < cnt; j++) {
					if (!(reses[no_dup[j]][0] == reses[i][0] && reses[no_dup[j]][1] == reses[i][1])) {
						continue;
					} else {
						break;
					}
				}
				if (j == cnt) {
					no_dup[cnt++] = i;
				}
			}
		}

		// 排序
		for (int i = 0; i < cnt; i++) {
			for (int j = i+1; j < cnt; j++) {
				if (reses[no_dup[i]][0] - reses[no_dup[j]][0] > 0.00001) {
					int tmp = no_dup[i];
					no_dup[i] = no_dup[j];
					no_dup[j] = tmp;
				} else if (fabs(reses[no_dup[i]][0] - reses[no_dup[j]][0]) < 0.00001) {
					if (reses[no_dup[i]][1] - reses[no_dup[j]][1] > 0.00001) {
						int tmp = no_dup[i];
						no_dup[i] = no_dup[j];
						no_dup[j] = tmp;
					}
				}
			}
		}
		// 输出
		printf("%d\n", cnt);
		for (int i = 0; i < cnt; i++) {
			double a = fabs(reses[no_dup[i]][0]) < 0.001? 0.0: reses[no_dup[i]][0];
			double b = fabs(reses[no_dup[i]][1]) < 0.001? 0.0: reses[no_dup[i]][1];
			printf("%.2f %.2f\n", a, b);
		}
	}
	return 0;
}

035 活着的数 (数学规律真奇妙)

题目

已知1和3是一个“活着的数”。
并且如果a和b是一个“活着的数”。
那么2+ab+2a+2b也是一个“活着的数”。
例如1和1是“活着的数”。
那么2+1+2+2=7也是一个活着的数。

分析

第一反应是递推,然后离线查找,但是n的范围是INT_MAX,所以显然不行。
这题要从数学等式上下功夫。
2 + ab +2a + 2b = c
4 +ab + 2a + 2b = c + 2
(a + 2)(b + 2) = c + 2
也就是说,任意“活着的数”+2 都是之前“活着的数”+2的乘积。所以对任何“活着的数”n,有n+2 = (1 + 2)p(3 + 2)q

解答

#include <iostream>
using namespace std;

int main(int argc, char *argv[]) {
	int n;
	while(scanf("%d", &n) != EOF) {
		n += 2;
		while (n % 5 ==0) {
			n /= 5;
		}
		while (n % 3 == 0) {
			n /= 3;
		}
		if (n == 1) {
			printf("Yes\n");
		} else {
			printf("No\n");
		}
	}	
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值