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
解题思路:
本题乍一看特别简单,用递归特别简单就是对的,这是所有人都能想到 方法,但是这个方法当n很大的时候,递归调用太多,很导致运行超时,而且,题目时间限制只有10ms,递归这种“神奇”方法不管用了,另辟蹊径,想到了,每一层的结果都存起来,之后用的时候直接拿出来,不用再从头开始,这样就会比较高效,可以把遇到的最大i值保留,如果后来输入的i小于最大值,O(1)就可以得到结果。否则,接着之前存起来的结果继续向后运算。
具体实现请看代码。
#include<stdio.h>
int main(){
int q;
int j,i;
int ent[10000];
scanf("%d",&q);
int n;
int m=0;
ent[0]=10;
for(j=0;j<q;j++){
scanf("%d",&n);
if(n>m)
{
for(i=m+1;i<=n;i++)//接着向后计算
{
ent[i]=ent[i-1]*11/10;
}
printf("%d\n",ent[n-1]);
m=n; //保留最大值
}
else
printf("%d\n",ent[n-1]);
}
return 0;
}
递归做法就不写出来啦。
二、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
解题思路:
这道题考查的是矩阵三元组,这种存储结构并不难。
三元组顺序表,用于稀疏矩阵的存储,因此经常被称为系数矩阵的三元组表示,它们是一个意思。
稀疏矩阵,即包含大量值为 0 的矩阵,此类矩阵的一种存储方法是将矩阵中所有非 0 元素所在的位置(行标和列标)和元素值存储在顺序表(数组)中,通过存储由所有非 0 元素的三元组构成的顺序表,以及该稀疏矩阵的行数和列数,即可完成对整个稀疏矩阵的存储,这就是稀疏矩阵使用三元组表示的实现思想。
因此,每个非 0 元素的三元组需要使用结构体进行自定义:
struct node{
int row;//非零元素行
int col;//非零元素列
int value; //非零元素
};
而在矩阵相加实现的过程中,最重要的是逻辑的清晰:本题中以行优先储存非零元素。值得注意的一点,就是在两个元素相加的0的时候,要记得不能把那个元素保留,直接跳过即可,这也是测试点中一个重要点。详细的逻辑我会写在我的代码中。如下:
#include<stdio.h>
struct node{
int row;
int col;
int value;
};
void sum(struct node A[],struct node B[],int count1,int count2,int R,int CO)
{
int count3=0;
int i=0,j=0;
struct node C[500001];
while(i<count1&&j<count2)
{
if(B[j].row==A[i].row) //先判断行数是否相等
{
if(B[j].col==A[i].col) //行数相等列数也相等则相加
{
if(B[j].value+A[i].value!=0) //和不为0才储存
{
C[count3].row=B[j].row;
C[count3].col=B[j].col;
C[count3].value=B[j].value+A[i].value;
count3++;
i++;
j++;
}
else
{
i++;j++;
}
}
else //行数相等列数不等
{
if(B[j].col<A[i].col) //如果B[j]列数小于A[i],证明B[j]在A[i]之前
{
C[count3].row=B[j].row;
C[count3].col=B[j].col;
C[count3].value=B[j].value;
count3++;
j++;
}
if(B[j].col>A[i].col) //如果B[j]列数大于A[i],证明B[j]在A[i]之后
{
C[count3].row=A[i].row;
C[count3].col=A[i].col;
C[count3].value=A[i].value;
count3++;
i++;
}
}
}
else //行数不等
{
if(B[j].row<A[i].row) 如果B[j]行数小于A[i],证明B[j]在A[i]之前
{
C[count3].row=B[j].row;
C[count3].col=B[j].col;
C[count3].value=B[j].value;
count3++;
j++;
}
if(B[j].row>A[i].row) 如果B[j]行数大于A[i],证明B[j]在A[i]之后
{
C[count3].row=A[i].row;
C[count3].col=A[i].col;
C[count3].value=A[i].value;
count3++;
i++;
}
}
}
while(j<count2) //j<count2时证明i==count1
{
C[count3].row=B[j].row;
C[count3].col=B[j].col;
C[count3].value=B[j].value;
count3++;
j++;
}
while(i<count1) //i<count1时,证明j==count2
{
C[count3].row=A[i].row;
C[count3].col=A[i].col;
C[count3].value=A[i].value;
count3++;
i++;
}
printf("%d %d %d\n",R,CO,count3);
for(i=0;i<count3;i++)
{
printf("%d %d %d\n",C[i].row,C[i].col,C[i].value);
}
}
int main()
{
int count1,count2,row1,row2,col1,col2;
int i;
scanf("%d %d %d",&row1,&col1,&count1);
struct node A[count1+1];
for(i=0;i<count1;i++)
{
scanf("%d %d %d",&A[i].row,&A[i].col,&A[i].value);
}
scanf("%d %d %d",&row2,&col2,&count2);
struct node B[count2+1];
for(i=0;i<count2;i++)
{
scanf("%d %d %d",&B[i].row,&B[i].col,&B[i].value);
}
if(row1!=row2||col1!=col2)
{
printf("Illegal!");
}
else
{
sum(A,B,count1,count2,row1,col1);
}
return 0;
}
三、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
解题思路:
首先分析题目中规定1前面是n,n后面是1,他形成了一个圈。就要想到循环。我最初做用的双向循环链表,在重新放置被指定位置的元素是,用到了跳舞链,不过用链表查找指定元素得从头遍历,与数组相比来说,特别的不方便。我刚开始同链表写的,出现了运行超时,内存超限,段错误的问题,应该是我双向循环链表没有用好,欢迎大佬来指教:
- 未通过代码:
#include<bits/stdc++.h>
using namespace std;
struct node{
int data;
struct node *left;
struct node *right;
};
int main()
{
int T,l;
//int a[10000];
int i,n,m;
scanf("%d",&T);
for(l=1;l<=T;l++)
{
scanf("%d %d",&n,&m);
char s[10];
int i,j,k;
struct node *p,*p0,*head=NULL;
struct node *q1,*q2,*k1,*k2;
p=(struct node*)malloc(sizeof(struct node));
p->data=2;
p0=(struct node*)malloc(sizeof(struct node));
p0->data=1;
p0->right=p;
p->left=p0;
p0->left=NULL;
p->right=NULL;
head=p0;
p0=p;
for(k=3;k<=n;k++)
{
p=(struct node*)malloc(sizeof(struct node));
p->data=k;
if(k!=n){
p->left=p0;
p->right=NULL;
p0->right=p;
}
else
{
p->left=p0;
p->right=head;
p0->right=p;
head->left=p;
}
p0=p;
}
for(k=1;k<=m;k++)
{
scanf("%s",s);
scanf("%d%d",&i,&j);
p=head;
q1=NULL;
k1=NULL;
if(s[0]=='A'||s[0]=='B')
{
while(q1==NULL||k1==NULL)
{
if(p->data==i)
{
q1=p;
}
if(p->data==j)
{
k1=p;
}
p=p->right;
}
if(s[0]=='A')
{
q1->left->right=q1->right;
q1->right->left=q1->left;
q1->right=k1;
q1->left=k1->left;
k1->left->right=q1;
k1->left=q1;
}
else
{
q1->left->right=q1->right;
q1->right->left=q1->left;
q1->right=k1->right;
q1->left=k1;
k1->right->left=q1;
k1->right=q1;
}
}
if(s[0]=='Q'){
if(i==0){
while(k1==NULL){
if(p->data==j){
k1=p->left;
printf("%d\n",k1->data);
}
p=p->right;
}
}
if(i==1){
while(k1==NULL)
{
if(p->data==j){
k1=p->right;
printf("%d\n",k1->data);
}
p=p->right;
}
}
}
}
}
return 0;
}
之后在别人讲解中获得启示,用了一个结构体数组,也算静态链表吧,储存查找都超级方便,data值也不用存,因为有下标,以下是我的正确代码。
#include<bits/stdc++.h>
using namespace std;
struct node{
int left;
int right;
};
int main()
{
int T,k;
struct node HT[10002];
char s[10];
int i,j;
int n,m;
scanf("%d",&T);
while(T--)
{
scanf("%d %d",&n,&m);
for(k=1;k<=n;k++)
{
HT[k].left=k-1;
HT[k].right=k+1;
}
HT[1].left=n;
HT[n].right=1;
for(k=1;k<=m;k++)
{
scanf("%s",s);
scanf("%d %d",&i,&j);
if(s[0]=='A')
{
HT[HT[i].right].left=HT[i].left;
HT[HT[i].left].right=HT[i].right;
HT[i].right=j;
HT[i].left=HT[j].left;
HT[HT[j].left].right=i;
HT[j].left=i;
}
else if(s[0]=='B')
{
HT[HT[i].right].left=HT[i].left;
HT[HT[i].left].right=HT[i].right;
HT[i].left=j;
HT[i].right=HT[j].right;
HT[HT[j].right].left=i;
HT[j].right=i;
}
else if(s[0]=='Q')
{
if(i==0)
printf("%d\n",HT[j].left);
else
{
printf("%d\n",HT[j].right);
}
}
// getchar();
}
}
return 0;
}
两个代码中 跳舞链部分
q1->left->right=q1->right;
q1->right->left=q1->left;
HT[HT[i].right].left=HT[i].left;
HT[HT[i].left].right=HT[i].right;
如果我写的有错误,欢迎大家交流指正。
四、幸福指数(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
解题思路:
要找最大幸福指数,幸福指数等于一个区间内所有幸福值的和乘以这个区间内最小值所得的值,如果一个区间一个区间找,一步步的挪着走,无疑是最暴力的解法,无疑是无法通过的低效的代码。因此我们要换一种想法。
要最大的幸福指数,如果我要以某个数a做区间内最小的数,那么,在以a为最小值的最长区间里(左边没有比它小的,右边也没有比它小的的情况),我们可以找到以a为最小值所对应的最大幸福指数(因为幸福指数为非负,在找到的最长区间内的子区间内幸福值的和与a的积一定≤最长区间内幸福值的和与a的乘积),按这种想法,找到每个幸福值作为最小元素对应的最大幸福指数,最后找出最大值。
那么,关键的一步还有怎么找出那个最大区间呢?用单调栈就可以解决,从左边遍历一遍求每个数左边第一个比它小的,从右边遍历一遍求每个数右边第一个比它小的数。
最后,遍历每一个幸福值,求以它为区间最小值所对应的最大幸福指数,再选出所有“最大”中的最大。
代码如下:
#include<bits/stdc++.h>
using namespace std;
long long H[100005];
int a[100005];
int le[100005];
int ri[100005];
stack<int> Find;
int n,l,r;
int main()
{
scanf("%d",&n);
//long long H[n+5];
//int a[n+5];
//int le[n+5];
//int ri[n+5];
a[0]=a[n+1]=-1e9;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
H[i]=a[i]+H[i-1];
}
Find.push(0); //寻找每个元素左面的第一个比它小的数
for(int i=1;i<=n;i++){
while(!Find.empty()&&a[Find.top()]>=a[i]){
Find.pop();
}
le[i]=Find.top();
Find.push(i);
}
while(!Find.empty())Find.pop();
Find.push(n+1); //寻找每个元素右面的第一个比它小的数
for(int i=n;i>=1;i--){
while(!Find.empty()&&a[Find.top()]>=a[i]){
Find.pop();
}
ri[i]=Find.top();
Find.push(i);
}
long long maxn=0;
for(int i=1;i<=n;++i){
if((H[ri[i]-1]-H[le[i]])*a[i]>maxn)
{
maxn=(H[ri[i]-1]-H[le[i]])*a[i];
l=le[i]+1;
r=ri[i]-1;
}
else if((H[ri[i]-1]-H[le[i]])*a[i]==maxn) //相等的话,看谁区间长 ,区间一样长,看谁靠左
{
if(r-l<ri[i]-1-le[i]-1)
{
maxn=(H[ri[i]-1]-H[le[i]])*a[i];
l=le[i]+1;
r=ri[i]-1;
}
else if(le[i]+1<l)
{
maxn=(H[ri[i]-1]-H[le[i]])*a[i];
l=le[i]+1;
r=ri[i]-1;
}
}
}
printf("%lld\n%d %d\n",maxn,l,r);
return 0;
}
嘻嘻收获:
此次实验课收获很大,有些知识真的是第一次用到,双向循环链表,跳舞链……虽然老师课上讲过,但我很惭愧没有自己把它实现,我会继续努力的!!
还有一个重大收获,当需要的数组很大很多时,局部函数内存会不够用,要考虑把他们放到全局(第四题)!!!