数据结构-第二次上机实验-解题报告
一、7-1数列查询
题目
已知数列的通项公式为:
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
思路
方法一:【递归】看到f(n)与f(n-1)的关系,很容易想到,用递归实现。不过,递归效率较低。只过了50%。
方法二:【枚举打表】既然递归能够实现,所以在递归失败后,我想到了用记录每一个n对应的f(n)的值的方式,来实现值的获取。(当然,要选效率高,我们就得确定最大的n值,进行枚举,否则枚举10000个,没有必要。故,用max[i]存储每个输入值,用来更新Max,以获得最大值)
代码
#include<iostream>
#include<string>
using namespace std;
int a[10001];
int main(){
int q;cin>>q;
int n;
a[1]=10;
int max[10001];
cin>>max[1];
int Max=max[1];
for(int i=2;i<=q;i++)
{
cin>>max[i];
if(Max<max[i]) Max=max[i];
}
for(int i=2;i<=Max;i++)
{
a[i]=a[i-1]*11/10;
}
for(int i=1;i<=q;i++)
{
printf("%d\n",a[max[i]]);
}
return 0;
}
感悟
当然,要想效率高,我们就得确定最大的n值进行枚举,否则枚举10000个,没有必要。故,用max[i]存储每个输入值,用来更新Max,以获得最大值。
还有一点,cout的效率比printf低下,这个问题将在for循环次数过大时放大。所以追求效率时,应当选取scanf与printf。
二、7-2稀疏矩阵之和
题干
矩阵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
思路
稀疏矩阵相加,通过老师的暗示 ,通过学习,我们知道,可以用三元组来存储稀疏矩阵来提高空间利用率,而不直接用二维数组的方式。
三元组代码
typedef struct{
int i,j;
int value;
}Three;//三元组结点
typedef struct
{
Three a[50001];
int imax,jmax;
int size;
}Tmatrix;//三元组表示的稀疏矩阵
通过题目的分析,显然应该分多类多层讨论。
矩阵A,B行列不相等不可相加减,故输出illegal,这个应该都能考虑到
可相加减时,将在代码中做详细分类讨论。
代码
#include<bits/stdc++.h>
using namespace std;
typedef struct{
int i,j;
int value;
}Three;
typedef struct
{
Three a[50001];
int imax,jmax;
int size;
}Tmatrix;
void initial(Tmatrix *T,int x,int y)
{
T->imax=x;
T->jmax=y;
T->size=0;
}
void insert_T(Tmatrix *T,int i,int j,int n)
{
T->size++;
T->a[T->size].i=i;
T->a[T->size].j=j;
T->a[T->size].value=n;
}
void Myprint(Tmatrix *T)
{
printf("%d %d %d\n",T->imax,T->jmax,T->size);
for(int i=1;i<=T->size;i++)
{
printf("%d %d %d\n",T->a[i].i,T->a[i].j,T->a[i].value);
}
}
void Add(Tmatrix *A,Tmatrix *B,Tmatrix *C)
{
int i=1,j=1;//此ij非彼ij
while(i<=A->size&&j<=B->size)//遍历AB数组
{
if(A->a[i].i<B->a[j].i)//A数组的第i个结点行数靠前,则先进入C组
{
insert_T(C,A->a[i].i,A->a[i].j,A->a[i].value);
i++;
}
else if(A->a[i].i==B->a[j].i)//行相同,下面比较列
{
if(A->a[i].j<B->a[j].j)//A[i]列数靠前,先入C组
{
insert_T(C,A->a[i].i,A->a[i].j,A->a[i].value);
i++;
}
else if(A->a[i].j>B->a[j].j)//反之亦然
{
insert_T(C,B->a[j].i,B->a[j].j,B->a[j].value);
j++;
}
else
{
if(A->a[i].value+B->a[j].value!=0) insert_T(C,B->a[j].i,B->a[j].j,B->a[j].value+A->a[i].value);//value值相加为0则删除
i++;j++;
}
}
else
{
insert_T(C,B->a[j].i,B->a[j].j,B->a[j].value);
j++;
}
}
while(i<=A->size)//处理剩余的结点
{
insert_T(C,A->a[i].i,A->a[i].j,A->a[i].value);
i++;
}
while(j<=B->size)
{
insert_T(C,B->a[j].i,B->a[j].j,B->a[j].value);
j++;
}
}
int main ()
{
Tmatrix A,B,C;
int n1,m1,t1;
int n2,m2,t2;
//int count=0;
for(int i=1;i<=50000;i++)//初始化
{
A.a[i].value=0;
B.a[i].value=0;
C.a[i].value=0;
}
cin>>n1>>m1>>t1;
initial(&A,n1,m1);
for(int i=1;i<=t1;i++)
{
A.size++;
scanf("%d %d %d",&A.a[i].i,&A.a[i].j,&A.a[i].value);
}
cin>>n2>>m2>>t2;
initial(&B,n2,m2);
for(int i=1;i<=t2;i++)
{
B.size++;
scanf("%d %d %d",&B.a[i].i,&B.a[i].j,&B.a[i].value);
}
if(n1!=n2||m1!=m2)//不合法
{
cout<<"Illegal!";
return 0;
}
initial(&C,n1,m1);
Add(&A,&B,&C);
Myprint(&C);
}
三、7-3文字编辑
题干:
一篇文章由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
思路分析
在上机实验时,我用一个数组,一个游标的方式,根据下标位置,来修改每个汉字的位置。由于每次移动汉字都需要对数组某一区间整体搬迁,故效率极其低下,又需要通过判断来输出,毛估估,在移动时就达到了O(n²)级别的时间复杂度,故,即使课下认真debug,也只过了前面三个点。
但课后老师提示了可以通过跳舞链来实现,一顿饿补之后,就有了下面的代码。
跳舞链
跳舞链,在我看来,可以算是一种静态双向循环链表(理解可能有些偏差)
跳舞链的删除,其实并没有改变该元素头尾指针,存在风险,但,我们正利用了这个风险,使插入时,更加简便。
部分代码如下:
void creat(int n)
{
for(int i=1;i<=n;i++)
{
//a[i]=i;
l[i]=i-1;
r[i]=i+1;
}
r[n]=1;
l[1]=n;
}
//下面为“摘下”操作
void del_A(int i,int j)//删除i元素,并改变i的左右指针以便插入j前
{
r[l[i]]=r[i];
l[r[i]]=l[i];
r[i]=j;
l[i]=l[j];
}
void del_B(int i,int j)//删除i元素,并改变i的左右指针以便插入j后
{
r[l[i]]=r[i];
l[r[i]]=l[i];
l[i]=j;
r[i]=r[j];
}
void ins(int x)//利用风险,在新位置挂上
{
r[l[x]]=x;
l[r[x]]=x;
}
完整代码如下:
至于ABQ是怎么执行的,其实有了跳舞链,就只是一删与一插的事。
#include<iostream>
#include<string>
using namespace std;
//int a[10001];//汉字
int l[10001];//左索引
int r[10001];//右索引
void creat(int n)
{
for(int i=1;i<=n;i++)
{
//a[i]=i;
l[i]=i-1;
r[i]=i+1;
}
r[n]=1;
l[1]=n;
}
void del_A(int i,int j)//删除i元素,并改变i的左右指针以便插入j前
{
r[l[i]]=r[i];
l[r[i]]=l[i];
r[i]=j;
l[i]=l[j];
}
void del_B(int i,int j)//删除i元素,并改变i的左右指针以便插入j后
{
r[l[i]]=r[i];
l[r[i]]=l[i];
l[i]=j;
r[i]=r[j];
}
void ins(int x)
{
r[l[x]]=x;
l[r[x]]=x;
}
void func(char s[],int i,int j,int n)
{
if(s[0]=='A')
{
del_A(i,j);
ins(i);
}else
if(s[0]=='B')
{
del_B(i,j);
ins(i);
}else
if(s[0]=='Q')
{
if(i==0)
{
printf("%d\n",l[j]);
}
else if(i==1)
{
printf("%d\n",r[j]);
}
}
}
int main()
{
int T;cin>>T;
int n,m;
char s[3];
int x,y;
for(int i=1;i<=T;i++)
{
scanf("%d%d",&n,&m);
creat(n);
for(int i=1;i<=m;i++)
{
scanf("%s%d%d",s,&x,&y);
// cout<<s<<x<<y<<endl;
func(s,x,y,n);
}
}
}
四、7-4幸福指数
题干
人生中哪段时间最幸福?幸福指数可能会帮你发现。幸福指数要求:对自己每天的生活赋予一个幸福值,幸福值越大表示越幸福。一段时间的幸福指数就是:这段时间的幸福值的和乘以这段时间的幸福值的最小值。幸福指数最大的那段时间,可能就是人生中最幸福的时光。
输入格式:
第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
思路分析:
不得不说,代码这方面,向大佬们学习还是有点用的,从大佬习得每个i可以对应一个最大幸福指数区间的思想,这样保证了外层是O(n)级别的时间复杂度。又通过对单调栈的学习,对区间的单调划分,也达到了O(n)级别,受益匪浅!
具体思路,将在代码中体现
代码如下:
#include<iostream>
#include<stack>
using namespace std;
long long int now=0;
long long int max_now= 0;
stack<int>lstack;//左边界栈
stack<int>rstack;//右边界栈
long long int a[100001];//单个值的值
long long int sum[100001];//从1到n区间的和值
long long int l[100001], r[100001];//存储各个值对应的左右区间
long long int L, R;//最终左右端点
int main()
{
int n;
scanf("%d", &n);
a[0] = a[n + 1] = -1;//设置左右端点为必定最小值
for (int i = 1; i <= n; i++)//求前缀和
{
scanf("%d", &a[i]);
sum[i] = sum[i - 1] + a[i];
}
lstack.push(0);//入栈,入数组下标
for (int i = 1; i <= n; i++)//遍历i的左区间,用单调栈找到小于i的数
{
while (a[lstack.top()] >= a[i])//小于栈顶,非空则弹栈
{
if (lstack.empty()) break;//空则结束
else lstack.pop();//非空则弹栈至大于栈顶
}
l[i] = lstack.top();//该值对应下标i的左区间数组更新为栈顶元素
lstack.push(i);//该值下标i入栈
}
rstack.push(n + 1);
for (int i = n; i >= 1; i--)//遍历i的右区间,用单调栈找到小于i的数
{
while (a[rstack.top()] >= a[i])//同上
{
if (rstack.empty())break;
else rstack.pop();
}
r[i] = rstack.top();
rstack.push(i);
}
for (int i = 1; i <= n; i++)//遍历n个区间,更新max求最大
{
now = (sum[r[i] - 1] - sum[l[i]]) * a[i];
if (now > max_now)//now大则更新
{
max_now = now;
L = l[i];
R = r[i];
}
else if (now == max_now && R - L < r[i] - 1 - l[i] - 1)//相等,则取最左最大
{
R = r[i];
L = l[i];
}
else if (now == max_now && l[i]+1< L)//同上
{
R = r[i];
L = l[i];
}
}
//for(int i=1;i<=n;i++) cout<<l[i]<<" ";
//cout<<endl;
//for(int i=1;i<=n;i++) cout<<r[i]<<" ";
printf("%lld\n%lld %lld\n",max_now,L+1,R-1);
}
后面注释掉的三行,用于调试两个栈是否出现错误。