JLU数据结构第二次上机实验解题报告

7-1 数列查询 (100 分)

     已知数列的通项公式为:

     f(n) = f(n-1)*11/10,f[1]=10. 

通项从左向右计算,*和/分别表示整数乘法和除法。 现在,要多次查询数列项的值。

输入格式:

第1行,1个整数q,表示查询的次数, 1≤q≤10000. 第2至q+1行,每行1个整数i,表示要查询f(i)的值。

输出格式:

q行,每行1个整数,表示f(i)的值。查询的值都在32位整数范围内。

输入样例:

在这里给出一组输入。例如:

3
1
2
3

输出样例:

在这里给出相应的输出。例如:

10
11
12

作者 谷方明

单位 吉林大学

代码长度限制 16 KB

时间限制 10 ms

内存限制 1 MB

解法一:

思路:

解决本题有两个关键问题:

1.最多查询到哪一项。

2.是从前向后查询吗,会不会乱序查询。

两个问题综合考虑,由于每一项都涉及前面一项,由递推关系,求某一项必须要知道前面的所有项,本题时间限制在10ms,首先大致感觉递归是不可能的,很可能被卡住,而且由于每次计算都对之后的计算有贡献,因此应当记录下来,避免重复计算。而如果可以了解到最多查询到哪一项,则可以最小化计算所花费的时间。因此考虑将所有查询的操作保存下来,维护一个不断更新的最大查询项,最后只需要一次计算到最大查询项,再按输入顺序输出查询即可。这种解法也解决了乱序查询的问题。如果是从前向后查询则可以不保存。但我还是认为不会那么简单,于是使用了这种方法。

代码实现:

由于逻辑简单,略去注释。

#include <iostream>
#include <vector>
using namespace std;
vector <int> pans;
int main() {
	int n = 0, max = 0, cup = 0;
	int* ans = new int[20000];
	ans[1] = 10;
	scanf("%d", &n);
	for (int i = 0; i < n; i++) {
		scanf("%d", &cup);
		if (cup > max) {
			max = cup;
		}
		pans.push_back(cup);
	}
	for (int i = 2; i <= max; i++) {
		ans[i] = ans[i - 1] * 11 / 10;
	}
	for (int i = 0; i < pans.size(); i++) {
		printf("%d\n", ans[pans[i]]);
	}
}

7-2 稀疏矩阵之和 (100 分)

矩阵A和B都是稀疏矩阵。请计算矩阵的和A+B.如果A、B不能做和,输出“Illegal!”

输入格式:

矩阵的输入采用三元组表示,先A后B。对每个矩阵:

第1行,3个整数N、M、t,用空格分隔,分别表示矩阵的行数、列数和非0数据项数,10≤N、M≤50000,t≤min(N,M).

第2至t+1行,每行3个整数r、c、v,用空格分隔,表示矩阵r行c列的位置是非0数据项v, v在32位有符号整型范围内。三元组默认按行列排序。

输出格式:

矩阵A+B,采用三元组表示,默认按行列排序,非零项也在32位有符号整型范围内。

输入样例:

10 10 3
2 2 2
5 5 5
10 10 20
10 10 2
2 2 1
6 6 6

输出样例:

10 10 4
2 2 3
5 5 5
6 6 6
10 10 20

作者 谷方明

单位 吉林大学

代码长度限制 16 KB

时间限制 100 ms

内存限制 10 MB

解法一:

思路:

解决本题关键有两点:

1.如何将稀疏矩阵按行列顺序存储。

2.如何将两个稀疏矩阵相加。

对于第一个问题,我一开始使用的方法是通过比较插入来保证行列序,这种方法在实际应用中更合适,但是解题时效率不足,且焦点太多,代码量大,很容易出现bug,就是因为这个写出bug导致整个过程我都在查找bug,而直到最后也没找出来。后来我改变思路,直接先存入,再使用sort排序,这样显著减少了犯错的概率。

 

对于第二个问题,参考归并排序的合并过程,之前我也写得很复杂,人为讨论各种情况,但后来,这里我使用了一点小技巧,因为行列小的要先存入,我这里认为如果一个矩阵已经合并结束,也不退出循环,而是将它代表结点的值赋值0,行列复制为无穷,这样也简化了代码的逻辑。减少犯错概率。

 

这里还有一个易忽略的点,就是可能有的节点值相加合并过后变成了0,根据稀疏矩阵定义则应该进行讨论,避免存入值为0的节点。

代码实现:

(可以看到注释掉的原本的代码冗杂不堪)

#include <iostream>
#include <vector>
#include <algorithm>
typedef struct {
	int row;
	int col;
	int val;
}Matrix;
bool com(Matrix a, Matrix b) {//排序函数
	if (a.row != b.row) {return a.row<b.row;}
	else if (a.col != b.col) { return a.col < b.col; }
}
using namespace std;
vector<Matrix> ans;
int min(int m, int n) {
	if (m > n) {
		return n;
	}
	else { return m; 
	}
}
bool Isalessb(Matrix A, Matrix B) {//比较a是否应该比b先存入
	if (A.row != B.row) { return A.row < B.row; }
	if (A.col != B.col) { return A.col < B.col; }
	return false;
}
bool Isaequalb(Matrix A, Matrix B) {//a,b是否相等
	if (A.row == B.row && A.col == B.col) {
		return true;
	}
	return false;
}
/*void banyun(int weizhi, Matrix juzhen[], int count) {
	for (int i = count; i > weizhi; i--) {
		juzhen[i] = juzhen[i - 1];
	}
}*/
/*void addval(int row, int col, int val, Matrix juzhen[], int count) {
	int i = 0;
	while (i < count) {
		if (juzhen[i].row < row) {
			i++;
		}
		else { break; }
	}
	
	int j = i;
	while (j < count) {
		if (juzhen[j].col < col) {
			j++;
		}
		else {
			break;
		}
	}
	banyun(j, juzhen, count);
	juzhen[j].row = row;
	juzhen[j].col = col;
	juzhen[j].val = val;
}*/
Matrix* newjuzhen(int M, int N, int t) {
	int row = 0, col = 0, val = 0;
	int countA = 0;
	Matrix* A = new Matrix[min(M, N)];
	for (int i = 0; i < t; i++) {
		scanf("%d%d%d", &row, &col, &val);
		A[i].col = col;
		A[i].row = row;
		A[i].val = val;
	}
	/*printf("\n");
	for (int i = 0; i < countA; i++) {
		printf("%d %d %d\n", A[i].row, A[i].col, A[i].val);
	}*/
	sort(A, A + t, com);
	return A;
}
void addjuzhen(Matrix* A, Matrix* B, int t, int t2) {
	/*int i = 0, j = 0, k = 0;

	while (i != t && j != t2) {
		if (A[i].row > B[j].row) {
			ans.push_back(B[j++]);
		}
		else if (A[i].row == B[i].row) {
			if (A[i].col > B[j].col) {
				ans.push_back(B[j++]);
			}
			else if (A[i].col == B[j].col) {
				if (A[i].val + B[j].val != 0) {
					ans.push_back(A[i]);
					ans.back().val = A[i].val + B[j].val;
				}
				i++; j++;
			}
			else {
				ans.push_back(A[i++]);
			}
		}
		else {
			ans.push_back(A[i++]);
		}
	}
	if (j == t2 && i == t) {
		return;
	}
	else if (j == t2) {
		while (i != t) {
			ans.push_back(A[i++]);
		}
	}
	else if (i == t) {
		while (j != t) {
			ans.push_back(B[j++]);
		}
	}*/
	int i = 0, j = 0, k = 0;//i控制A的读取,j控制B的读取
	Matrix cupa, cupb;
	while (i != t || j != t2) {//a,b有一个不结束就迭代
		if (i != t) {            //已结束的赋特殊值,保证循环简单但又不会影响结果
			cupa.row = A[i].row;
			cupa.col = A[i].col;
			cupa.val = A[i].val;
		}
		else {
			cupa.row = 10000000;
			cupa.col = 10000000;
			cupa.val = 0;
		}
		if (j != t2) {
			cupb.row = B[j].row;
			cupb.col = B[j].col;
			cupb.val = B[j].val;
		}
		else {
			cupb.row = 10000000;
			cupb.col = 10000000;
			cupb.val = 0;
		}
		if (Isalessb(cupa, cupb)) {
			ans.push_back(cupa);
			i++;
		}
		else if (Isaequalb(cupa, cupb)) {   // 相等就合在一起
			if (cupa.val + cupb.val != 0) {//抵销不存
				Matrix cuppp;
				ans.push_back(Matrix());
				ans.back().val = cupa.val + cupb.val;
				ans.back().row = cupa.row;
				ans.back().col = cupa.col;
			}
			i++; j++;
		}
		else {//b行列顺序更小就存b
			ans.push_back(cupb);
			j++;
		}
	}
}
int main() {
	int M = 0, N = 0, t = 0;
	int M2 = 0, N2 = 0, t2 = 0;
	scanf("%d%d%d", &M, &N, &t);
	Matrix* A = newjuzhen(M, N, t);
	scanf("%d%d%d", &M2, &N2, &t2);
	if (M != M2 || N != N2) {//如果不可以相加,输出
		printf("Illegal!");
	}
	else {
		Matrix* B = newjuzhen(M2, N2, t2);
		addjuzhen(A, B, t, t2);

		printf("%d %d %d\n", M, N, ans.size());
		for (int i = 0; i < ans.size(); i++) {
			printf("%d %d %d", ans[i].row, ans[i].col, ans[i].val);
			if (i < ans.size() - 1) {
				printf("\n");
			}
		}
    }
}

7-3 文字编辑 (100 分)

一篇文章由n个汉字构成,汉字从前到后依次编号为1,2,……,n。 有四种操作:

A i j表示把编号为i的汉字移动编号为j的汉字之前;

B i j表示把编号为i的汉字移动编号为j的汉字之后;

Q 0 i为询问编号为i的汉字之前的汉字的编号;

Q 1 i为询问编号为i的汉字之后的汉字的编号。

规定:1号汉字之前是n号汉字,n号汉字之后是1号汉字。

输入格式:

第1行,1个整数T,表示有T组测试数据, 1≤T≤9999.

随后的每一组测试数据中,第1行两个整数n和m,用空格分隔,分别代表汉字数和操作数,2≤n≤9999,1≤m≤9999;第2至m+1行,每行包含3个常量s、i和j,用空格分隔,s代表操作的类型,若s为A或B,则i和j表示汉字的编号,若s为Q,i代表0或1,j代表汉字的编号。

输出格式:

若干行,每行1个整数,对应每个询问的结果汉字编号。

输入样例:

在这里给出一组输入。例如:

1 
9999 4 
B 1 2  
A 3 9999
Q 1 1
Q 0 3

输出样例:

在这里给出相应的输出。例如:

4
9998

作者 谷方明

单位 吉林大学

代码长度限制 16 KB

时间限制 1000 ms

内存限制 2 MB

解法一:

思路:

解决本题的关键有两点:

1.如何高效地插入与删除。

2.如何高效地通过序号找到结点。

对于第一个问题,容易想到使用链表,但根据题目要求,使用单链表的话对于询问i之前的汉字编号会很困难,可能拖累整个代码的效率,事实上我也尝试过,确实会在这一个点上造成效率的瓶颈。而且讨论细节的特殊情况会很痛苦,极有可能写出bug。于是想到使用双向循环链表,不用考虑任何特殊情况。

 

对于第二个问题,一开始我使用的是遍历链表来查找,结果发现这样的开销十分巨大,于是想到用一个数组预先存放所有结点,按编号从小到大初始化结点编号,因此定位某个结点只需要通过下标,在O(1)的开销下即可完成查找特定编号的结点。而且个人认为这里还有个降低开销的技巧,一开始开一个9999大小的结构体数组,然后赋予编号,数组也可以管理,之后下一轮查询不用再删除和分配以及初始化了,只需要顺着数组将指针重新定位即可。但是这样的优化在运行时间上好像没有特别直观的反馈,保留疑问。

这里还有另一个疑问,在pta上提交时由于最后一个点内存有时会不足,因此会导致答案错误,可是在运行过程中并没有申请多余空间,为什么内存会变大?

代码实现:

#include <iostream>
#include <list>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <map>
using namespace std;
typedef struct Node{
	int val;
	struct Node* left, * right;
} Node;
Node* headd;
inline Node* Creat(int n) {//整理指针
	headd[0].left = &headd[n - 1];
	headd[0].right = &headd[1];
	headd[n - 1].right = &headd[0];
	headd[n - 1].left = &headd[n - 2];
	for (int i = 1; i < n-1; i++) {
		headd[i].left = &headd[i - 1];
		headd[i].right = &headd[i + 1];
	}
	return headd;
}
void xinjian() {            //新建一个9999大小的数组
	headd = new Node[9999];
	for (int i = 0; i < 9999; i++) {
		headd[i].val = i + 1;
	}
}
inline void Insert(Node* a,int n,Node* head) {
	Node* p = head;        //向前插入
	p = &headd[n-1];
	a->left = p->left;
	a->right = p;
	a->left->right = a;
	a->right->left = a;
}
inline void Insertback(Node* a, int n, Node* head) {
	Node* p = head;        //向后插入
	p = &headd[n-1];
	p = p->right;
	a->left = p->left;
	a->right = p;
	a->left->right = a;
	a->right->left = a;
}
inline Node* quxia(int n, Node* head) {
	Node* p = head;        //取下某个节点
	p = &headd[n - 1];
	p->left->right = p->right;
	p->right->left = p->left;
	return p;
}
inline void A(int i, int j,Node* head) {//A的实现
	Node *p=quxia(i, head);
	Insert(p, j, head);
}
inline void B(int i, int j, Node* head) {//B的实现
	Node* p = quxia(i, head);
	Insertback(p, j, head);
}
inline void Q(int mode, int i,Node* head) {//Q的实现
	Node* p = head;
	if (mode == 0) {
		p = &headd[i - 1];
		p = p->left;
		printf("%d\n", p->val);
	}
	else if (mode == 1) {
		p = &headd[i - 1];
		printf("%d\n", p->right->val);
	}
}
int main() {
	xinjian();
	int T = 0;
	int m = 0, n = 0;
	int ii = 0, jj = 0;
	scanf("%d", &T);
	for (int i = 0; i < T; i++) {
		scanf("%d%d", &m, &n);
		Node* delhead = Creat(m);
		for (int j = 0; j < n; j++) {
			char MODE = '0';
			while (1) {        //确保读入正确
				MODE = getchar();
				if (MODE == 'A' || MODE == 'B' || MODE == 'Q') {
					break;
				}
			}
			scanf("%d%d" ,& ii, &jj);
			if (MODE == 'A') {
				A(ii, jj, delhead);
			}
			else if (MODE == 'B') {
				B(ii, jj, delhead);
			}
			else {
				Q(ii, jj, delhead);
			}
		}
	}
}

解法二:

关键点参考解法一

思路:

不同点在于最开始我依然想使用STL中的list来简化编写代码的逻辑。

迭代器在list中就相当于指针。

主要利用insert和erase以及find方法。

问题:查找特定编号的节点效率低下。每轮查询必须释放和重新申请空间,怀疑会大幅折损效率。

因此这个解法后两个点会超时。

代码实现:

#include <iostream>
#include <list>
using namespace std;
list<int> lianbiao;
list<int>::iterator check(list<int>::iterator it) {
	list<int>::iterator beginsss = lianbiao.begin();
	advance(beginsss, -1);
	list<int>::iterator endsss = lianbiao.end();
	advance(endsss, -1);
	if (it == lianbiao.end()) {
		return lianbiao.begin();
	}
	else if (it == beginsss) {
		return endsss;
	}
	else { return it; }
}
void A(int i, int j) {
	list<int>::iterator jj, ii;
	jj = find(lianbiao.begin(), lianbiao.end(), j);
	ii = find(lianbiao.begin(), lianbiao.end(), i);
	if (jj == lianbiao.begin()) {
		lianbiao.push_back(*ii);
	}
	else {
		lianbiao.insert(jj, *ii);
	}
	lianbiao.erase(ii);
}
void B(int i, int j) {
	list<int>::iterator jj, ii;
	jj = find(lianbiao.begin(), lianbiao.end(), j);
	ii = find(lianbiao.begin(), lianbiao.end(), i);
	if (*jj == lianbiao.back()) {
		lianbiao.push_front(*ii);
	}
	else {
		jj++;
		lianbiao.insert(jj, *ii);
	}
	lianbiao.erase(ii);
}
int Q(int mode, int i) {
	list<int>::iterator it;
	it = find(lianbiao.begin(), lianbiao.end(), i);
	//printf("%d", lianbiao.end());
	if (mode == 0) {
		if (it == lianbiao.begin()) {
			return lianbiao.back();
		}
		else {
			//advance(it, -1);
			return *(--it);
		}
	}
	else {
		if (*it == lianbiao.back()) {
			return lianbiao.front();
		}
		else {
			//advance(it, 1);
			return *(++it);
		}
	}
}
int main() {
	int T = 0;
	char mode = '0';
	int ii = 0, jj = 0;
	scanf("%d", &T);
	for (int i = 0; i < T; i++) {
		int n = 0, m = 0;
		scanf("%d%d", &n, &m);
		for (int q = 1; q <= n; q++) {
			lianbiao.push_back(q);
		}
		for (int j = 0; j < m; j++) {
			while (1) {
				mode = getchar();
				if (mode == 'B' || mode == 'Q' || mode == 'A') {
					break;
				}
			}
			scanf("%d%d", &ii, &jj);
			if (mode == 'A') {
				A(ii, jj);
			}
			else if (mode == 'B') {
				B(ii, jj);
			}
			else {
				printf("%d\n", Q(ii, jj));
			}
		}
		lianbiao.clear();
	}
}

7-4 幸福指数 (100 分)

人生中哪段时间最幸福?幸福指数可能会帮你发现。幸福指数要求:对自己每天的生活赋予一个幸福值,幸福值越大表示越幸福。一段时间的幸福指数就是:这段时间的幸福值的和乘以这段时间的幸福值的最小值。幸福指数最大的那段时间,可能就是人生中最幸福的时光。

输入格式:

第1行,1个整数n,, 1≤n≤100000,表示要考察的天数。

第2行,n个整数Hi,用空格分隔,Hi表示第i天的幸福值,0≤n≤1000000。

输出格式:

第1行,1个整数,表示最大幸福指数。

第2行,2个整数l和r,用空格分隔,表示最大幸福指数对应的区间[l,r]。如果有多个这样的区间,输出最长最左区间。

输入样例:

在这里给出一组输入。例如:

7
6 4 5 1 4 5 6

输出样例:

在这里给出相应的输出。例如:

60
1 3

作者 谷方明

单位 吉林大学

代码长度限制 16 KB

时间限制 100 ms

内存限制 64 MB

解法一:

思路:

解决这个问题的关键有两点:

1.如何高效计算出某个区间内所有值的和。

2.如何快速确定某个值在哪个区间内为最小值。

 

对于第一点,在读入时存两个数组,第一个是原数组,第二个数组是前项和数组,保存了某个下标及之前的所有项之和。这样每次只需O(1)的代价就能得到某个区间的所有值之和。

 

对于第二点,在求某个数在哪个区间为最小值时,可以利用单调栈。即:维护一个单调递增的栈,那么每次只要发现入栈元素大于栈顶,栈顶元素保存并弹出后新的栈顶元素就是原数组中第一个小于它的值,向右访问(也就是正准备入栈但还没入栈这个值)则得到原数组中右边第一个小于它的值。

技巧:这个地方为了简化规则,我们在原数组的最左边和最右边都增加一个0元素,前项和数组的第一项也设为0。这样就无需对边界情况进行特殊探讨。减小出错概率及工作量。

反思:第二个测试点是全为0的数,之前我没有考虑,导致一直没有通过。

代码实现:

#include <iostream>
#include <vector>
using namespace std;
int* shuzu = new int[100003];
long long* sumshuzu = new long long[100003];
int l = 0, r = 0;
long long maxs = 0;							//存答案,l为最大区间左边界-1,r为右边界+1,maxs为最大值 输出时l+1,r-1
int cupl = 0, cupr = 0;
long long cupmax = 0;					//带cup的存的是当前算出来的,用update比较
void update(int now) {											
	cupmax = (sumshuzu[cupr - 1] - sumshuzu[cupl])*shuzu[now];
	if (cupmax > maxs) {
		maxs = cupmax;
		l = cupl;
		r = cupr;
		return;
	}
	if (cupmax == maxs&&cupr-cupl>r-l) {
		l = cupl;
		r = cupr;
		return;
	}
}
int main() {
	vector<int> stk;
	int n = 0;
	scanf("%d", &n);
	shuzu[0] = 0;								//在第一个元素前和最后一个元素前添加0,避免讨论
	shuzu[n + 1] = 0;
	sumshuzu[0] = 0;
	for (int i = 1; i < n + 1; i++) {
		scanf("%d", &shuzu[i]);						//读入
		sumshuzu[i] = shuzu[i] + sumshuzu[i - 1];	//边存边算前n项和
	}
	int count = 1;									//由于已经放了一个进去,count=1
	stk.push_back(0);								//先把第一个0入栈,有n+2个元素,所以count<n+2
	for(count;count<n+2;count++){
		while (!stk.empty() && shuzu[stk.back()] > shuzu[count]) {//栈空或入栈元素大于等于栈顶则直接入栈
			int now = stk.back();
			stk.pop_back();
			cupl = stk.back();										//cupl为左边第一个小于当前中间值的下标
			cupr = count;											//cupr为右边第一个小于当前值的下标
			update(now);											//update函数更新 
		}
		stk.push_back(count);
	}
	if (r != 0) {
		printf("%lld\n", maxs);
		printf("%d %d", l + 1, r - 1);
	}
	else {
		printf("%d\n", 0);
		printf("%d %d", 1, n);
	}
}

解法二:

思路:

关键点同解法一

对于每一个节点都向左向右扫描,找到第一个小于它的值或碰到边界。然后计算,并按照条件进行更新,更新维护最大值,最大最左的区间。

只适合争取分数,后两个点超时无法通过。这个题100ms的时间也明确了就是要用某种高效的办法解。

代码实现:

(代码丢了,假装有代码)不过这个应该都能实现~

总结:

1.要学会用各种技巧简化代码逻辑,避免焦点过多而出现bug,最后陷入bug之中!

2.要灵活运用各种数据结构,熟悉各种数据结构的独特性质,将题目抽象,用特定结构加以解决。

3.没有思路时也要想办法暴力拿分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值