JLU数据结构荣誉课——第二次实验
一.7-1 数列查询 (100 分)
已知数列的通项公式为:
f[1]=10;
f(n)=f(n-1)*11/10
通项从左向右计算,*和/分别表示整数乘法和除法。 现在,要多次查询数列项的值。
作者 谷方明
单位 吉林大学
代码长度限制 16 KB
时间限制 10 ms
内存限制 1 MB
输入格式:
第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
题目解析
根据通项公式求数列第i项
分析
这道题最直接的想法可能是用两重循环或者循环套递归。但试过的同学都知道,会超时。
我们可以注意到,因为查询次数<10000,比如当我们查询第100项后再去查询第50项,我们其实一直再做重复的计算,那如何缩短时间呢,我们自然会想到将计算过的数字保存下来,或者,将序列的每一项都保存下来,需要的时候直接去取用。
一共有多少项呢,因为有32位整数的限制,最大可以到202项,这样我们的思路就清晰了,先将前202项算出来,需要查询哪一项就直接输出。
代码实现如下
#include<stdio.h>
int main()
{
int n,i;
scanf("%d",&n);
int a[203];
int b[n+1];
a[1]=10;
for(i=2;i<=202;i++)
{
a[i]=a[i-1]*11/10;
}
for(i=1;i<=n;i++)
{
scanf("%d",&b[i]);
}
printf("%d",a[b[1]]);
for(i=2;i<=n;i++)
printf("\n%d",a[b[i]]);
}
二.7-2 稀疏矩阵之和 (100 分)
矩阵A和B都是稀疏矩阵。请计算矩阵的和A+B.如果A、B不能做和,输出“Illegal!”
作者 谷方明
单位 吉林大学
代码长度限制 16 KB
时间限制 10 ms
内存限制 1 MB
输入格式:
矩阵的输入采用三元组表示,先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,不链入,两指针后移。若两节点数值相加不为零,任一节点链入,数值等于两节点数值和,两指针后移。(注意总节点数的改变,前者-1,后者-2)
代码实现如下
#include<stdio.h>
#include<malloc.h>
struct Node
{
int hang;
int lie;
int num;
struct Node *next;
};
int main()
{
int N1,M1,t1;
scanf("%d%d%d",&N1,&M1,&t1);
Node *head1,*p0,*p1;
head1=(Node*)malloc(sizeof(Node));
p0=(Node*)malloc(sizeof(Node));
scanf("%d%d%d",&p0->hang,&p0->lie,&p0->num);
head1->next=p0;
for(int i=1;i<t1;i++)
{
p1=(Node*)malloc(sizeof(Node));
scanf("%d%d%d",&p1->hang,&p1->lie,&p1->num);
p0->next=p1;
p0=p1;
}
p0->next=NULL;
int N2,M2,t2;
scanf("%d%d%d",&N2,&M2,&t2);
if(N2!=N1||M2!=M1)
{
printf("Illegal!");
return 0;
}
Node *head2,*p2,*p3;
head2=(Node*)malloc(sizeof(Node));
p2=(Node*)malloc(sizeof(Node));
scanf("%d%d%d",&p2->hang,&p2->lie,&p2->num);
head2->next=p2;
for(int i=1;i<t2;i++)
{
p3=(Node*)malloc(sizeof(Node));
scanf("%d%d%d",&p3->hang,&p3->lie,&p3->num);
p2->next=p3;
p2=p3;
}
p2->next=NULL;
Node *head3;
int t_=t1+t2;
head3=(Node*)malloc(sizeof(Node));
head3->next=NULL;
p1=(Node*)malloc(sizeof(Node));
p2=(Node*)malloc(sizeof(Node));
p3=(Node*)malloc(sizeof(Node));
p3=head3;
while(head1->next!=NULL||head2->next!=NULL)
{
p1=head1->next;
p2=head2->next;
if(head1->next==NULL)
{
head2->next=head2->next->next;
p3->next=p2;
p2->next=NULL;
p3=p2;
}
else if(head2->next==NULL)
{
head1->next=head1->next->next;
p3->next=p1;
// printf("\n%d\n",p1->hang);
p1->next=NULL;
p3=p1;
}
else if(p1->hang==p2->hang&&p1->lie==p2->lie)
{
head2->next=head2->next->next;
head1->next=head1->next->next;
t_--;
if(p1->num+p2->num!=0)
{
p3->next=p1;
p1->num+=p2->num;
p1->next=NULL;
p3=p1;
}
else{t_--;}
}
else if(p1->hang<p2->hang||p1->hang==p2->hang&&p1->lie<p2->lie)
{
head1->next=head1->next->next;
p3->next=p1;
p1->next=NULL;
p3=p1;
}
else
{
head2->next=head2->next->next;
p3->next=p2;
p2->next=NULL;
p3=p2;
}
}
printf("%d %d %d",N1,M1,t_);
p0=head3->next;
while(p0)
{
printf("\n%d %d %d",p0->hang,p0->lie,p0->num);
p0=p0->next;
}
}
方法二
因为题干有提到三元组默认按行列排序,我们在上一个方法中没有考虑非有序输入的情况。那如果我们多想一步,考虑非有序输入呢?
如果我们还是使用链表,在链入时排序,将会使时间复杂度达到O(n^2)。
于是想到了用数组的方式实现,借助系统的快排,可以将时间复杂度缩小至O(nlogn)。
具体的遍历,插入规则,和链表类似。
代码实现如下
#include <bits/stdc++.h>
using namespace std;
struct Node
{
int hang;
int lie;
int num;
};
bool pd(Node a,Node b)
{
if(a.hang<b.hang||a.hang==b.hang&&a.lie<b.lie)
return 1;
else return 0;
}
int main()
{
int N1,M1,t1,N2,M2,t2;
scanf("%d%d%d",&N1,&M1,&t1);
Node node1[t1];
for(int i=0;i<t1;i++)
scanf("%d%d%d",&node1[i].hang,&node1[i].lie,&node1[i].num);
sort(node1,node1+t1,pd);
scanf("%d%d%d",&N2,&M2,&t2);
Node node2[t2];
for(int i=0;i<t2;i++)
scanf("%d%d%d",&node2[i].hang,&node2[i].lie,&node2[i].num);
sort(node2,node2+t2,pd);
if(N2!=N1||M2!=M1)
{
printf("Illegal!");
return 0;
}
int t_=t1+t2;
Node node3[t_];
int i1=0,i2=0,i=0;
while(i1!=t1||i2!=t2)
{
if(i1==t1)
{
node3[i++]=node2[i2++];
}
else if(i2==t2)
{
node3[i++]=node1[i1++];
}
else if(node1[i1].hang<node2[i2].hang||node1[i1].hang==node2[i2].hang&&node1[i1].lie<node2[i2].lie)
{
node3[i++]=node1[i1++];
}
else if(node1[i1].hang==node2[i2].hang&&node1[i1].lie==node2[i2].lie)
{
node3[i]=node2[i2];
node3[i].num+=node1[i1].num;
i++;
i1++;
i2++;
t_--;
if(node3[i-1].num==0)
{
t_--;
i--;
}
}
else
{
node3[i++]=node2[i2++];
}
}
printf("%d %d %d",N1,M1,t_);
for(i=0;i<t_;i++)
printf("\n%d %d %d",node3[i].hang,node3[i].lie,node3[i].num);
}
三.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号汉字。
作者 谷方明
单位 吉林大学
代码长度限制 16 KB
时间限制 10 ms
内存限制 1 MB
输入格式:
第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个整数,对应每个询问的结果汉字编号。
输入样例:
在这里给出一组输入。例如:
9999 4
B 1 2
A 3 9999
Q 1 1
Q 0 3
输出样例:
在这里给出相应的输出。例如:
4
9998
题目解析
改变文本中汉字的分布.连接顺序,并输出其前后位置的汉字编号。
分析
我拿到这道题最开始的思路,是用一个数组模拟所有汉字,在数组中完成对应的操作,但刚开始写就发现行不通。首先,对汉字移动操作的时间复杂度就达到了O(mn),而且如果只用一个数组,在交换后,无法根据编号直接对应到每个汉字,需要辅助数组,很不方便。
后来便想到了用结构体数组,将该汉字编号及其前驱后继储存在一起,再运用“跳舞链”完成移动操作。
代码实现如下
#include<stdio.h>
struct Node
{
int left;
int right;
};
int main()
{
int MM=0;
int T,i,j,t;
scanf("%d",&T);
for(t=0;t<T;t++)
{
int n,m;
scanf("%d%d",&n,&m);
Node node[n+1];
for(i=0;i<=n;i++)
{
node[i].left=i-1;
node[i].right=i+1;
}
node[1].left=n;
node[n].right=1;
for(j=0;j<m;j++)
{
char ch;
int i1,j1;
scanf("\n%c%d%d",&ch,&i1,&j1);
if(ch=='A')
{
node[node[i1].left].right=node[i1].right;
node[node[i1].right].left=node[i1].left;
node[node[j1].left].right=i1;
node[i1].left=node[j1].left;
node[i1].right=j1;
node[j1].left=i1;
}
else if(ch=='B')
{
node[node[i1].left].right=node[i1].right;
node[node[i1].right].left=node[i1].left;
node[i1].right=node[j1].right;
node[node[j1].right].left=i1;
node[i1].left=j1;
node[j1].right=i1;
}
else if(ch=='Q'&&i1==0)
{
if(MM==0)
{
printf("%d",node[j1].left);
MM++;
}
else printf("\n%d",node[j1].left);
}
else if(ch=='Q'&&i1==1)
{
if(MM==0)
{
printf("%d",node[j1].right);
MM++;
}
else printf("\n%d",node[j1].right);
}
}
}
}
三.7-4 幸福指数 (100 分)
人生中哪段时间最幸福?幸福指数可能会帮你发现。幸福指数要求:对自己每天的生活赋予一个幸福值,幸福值越大表示越幸福。一段时间的幸福指数就是:这段时间的幸福值的和乘以这段时间的幸福值的最小值。幸福指数最大的那段时间,可能就是人生中最幸福的时光。
作者 谷方明
单位 吉林大学
代码长度限制 16 KB
时间限制 10 ms
内存限制 1 MB
输入格式:
第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。故该方法合理。
继续分析
那么应该怎样获得以每一个元素为最小值的最大区间呢?我们需要找到该元素左边第一个比它小的值和右边第一个比它小的值。相信这个算法大家都比较熟悉,在之前学习线性表时做过练习,用单调栈来实现。
从前往后,从后往前,进行两次操作,就可以求出最大区间。
需要注意的是,如果到最后再根据区间的前后界将区间元素相加,可能会出现超时,每个区间都分开相加,其时间复杂度是O(n^2)。故我们需要提前求出前i项的和,再通过区间后界减前界,这样可以达到O(n)的时间复杂度。
当然这里有一些小细节,比如幸福值会超过32位整型(这是我用3个小时的代价发现的。。。。)还有全局的空间和main函数的空间是分开的,开大数组放在main函数里可能出现段错误,也就是空间超限(对dalao来说应该是常识,但我是才知道的//小声哔哔)
具体代码实现
#include <bits/stdc++.h>
using namespace std;
long long int sum[100005],happy=-1000000;
int a[100005];
int main()
{
int n,i,j,left_,right_;
scanf("%d",&n);
int left[n+1],right[n+1];
left[1]=0;
right[n]=n+1;
sum[0]=0;
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum[i]=a[i]+sum[i-1];
}
stack<int >skt;
skt.push(1);
for(i=2;i<=n;i++)
{
while(!skt.empty()&&a[skt.top()]>=a[i])
skt.pop();
if(skt.empty())
left[i]=0;
else left[i]=skt.top();
skt.push(i);
}
while(!skt.empty())
skt.pop();
skt.push(n);
for(i=n-1;i>=1;i--)
{
while(!skt.empty()&&a[skt.top()]>=a[i])
skt.pop();
if(skt.empty())
right[i]=n+1;
else right[i]=skt.top();
skt.push(i);
}
for(i=1;i<=n;i++)
{
long long int t;
t=sum[right[i]-1]-sum[left[i]];
if(t*a[i]>happy||t*a[i]==happy&&right[i]-left[i]>right_-left_+2)
{
happy=t*a[i];
right_=right[i]-1;
left_=left[i]+1;
}
}
printf("%lld\n%d %d",happy,left_,right_);
}