【JLU数据结构荣誉课】第六次上机实验
->点我进原题
-> 7-1 高精度数加法
-> 7-2 二叉树加权距离
-> 7-3 修轻轨
-> 7-4 数据结构设计I
7-1 高精度数加法 (100 分)
代码长度限制 \(16 KB\)
时间限制 \(100 ms\)
内存限制 \(1 MB\)
Description
高精度数是指大大超出了标准数据类型能表示的范围的数,例如10000位整数。很多计算问题的结果都很大,因此,高精度数极其重要。
一般使用一个数组来存储高精度数的所有数位,数组中的每个元素存储该高精度数的1位数字或多位数字。 请尝试计算:\(N\) 个高精度数的加和。这个任务对于在学习数据结构的你来说应该是小菜一碟。
Input
第 \(1\) 行,\(1\) 个整数 \(N\),表示高精度整数的个数,\((1≤N≤10000)\)。
第 \(2\) 至 \(N+1\) 行,每行1个高精度整数 \(x\), \(x\) 最多100位。
Output
1行,1个高精度整数,表示输入的 \(N\) 个高精度数的加和。
Sample Input
3
12345678910
12345678910
12345678910
Sample Output
37037036730
思路
高精度加法,这里不再赘述,但是考试时用的string,很玄学的有一个点一直RE,所以改成了char数组。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
inline int read(){
int x=0,f=0;
char ch=getchar();
while(!isdigit(ch)) f|=(ch=='-'),ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return f?-x:x;
}
int qa[1000],qb[1000],ans[1000],n,len1,len2;
int tot;
char a[1000], b[1000];
inline bool jud(int a){
if(a>9)
return 1;
return 0;
}
signed main(){
tot = read();
cin>>a;
if(tot==1){
cout<<a;
return 0;
}
for(int i=2;i<=tot;++i){
cin>>b;
// cout << a << endl << b << endl;
len1=strlen(a);
len2=strlen(b);
for(int i=0;i<1000;++i) qa[i]=qb[i]=ans[i]=0;
n=max(len1,len2);
for(int i=1;i<=n;++i){
if(len1<i) qa[i]=0;
else qa[i]=a[len1-i]-'0';
if(len2<i) qb[i]=0;
else qb[i]=b[len2-i]-'0';
}
// for(int i=n;i;--i) cout<<qa[i];
// for(int i=n;i;--i) cout<<qb[i];
for(int i=1;i<=n;++i){
ans[i]+=qa[i]+qb[i];
if(jud(ans[i])){
ans[i]%=10;
ans[i+1]++;
if(i==n) n++;
}
}
for(int i=0;i<n;++i) a[i]=ans[n-i]+'0';
a[n+1]='\0';
}
for(int i=n;i;--i) printf("%d", ans[i]);
return 0;
}
7-2 二叉树加权距离 (100 分)
代码长度限制 \(16 KB\)
时间限制 \(100 ms\)
内存限制 \(10 MB\)
Description
二叉树结点间的一种加权距离定义为:上行方向的变数 \(×3\) \(+\) 下行方向的边数 \(×2\) 。上行方向是指由结点向根的方向,下行方向是指与由根向叶结点方向。 给定一棵二叉树 \(T\) 及两个结点 \(u\) 和 \(v\),试求 \(u\) 到 \(v\) 的加权距离。
Input
第1行,1个整数 \(N\),表示二叉树的结点数,\((1≤N≤100000)\)。
随后若干行,每行两个整数 \(a\) 和 \(b\),用空格分隔,表示结点 \(a\) 到结点 \(b\) 有一条边,\(a\)、\(b\) 是结点的编号,\(1≤a,b≤N\);根结点编号为 \(1\),边从根向叶结点方向。
最后1行,两个整数 \(u\) 和 \(v\),用空格分隔,表示所查询的两个结点的编号,\(1≤u、v≤N\)。
Output
1行,1个整数,表示查询的加权距离。
Sample Input
5
1 2
2 3
1 4
4 5
3 4
Sample Output
8
思路
(1)LCA,在上跳下跳时记录走过路径个数,并乘以对应边权,较复杂,这里不推荐;
(2)最短路,将二叉树抽象成一个正向边权为2,反向边权为3的双向图,求点 \(u\) 到 \(v\) 的最短路即可,下面给出该做法的代码。
代码
#include<iostream>
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cmath>
#include<queue>
#define N 500010
#define inf 2147483647
using namespace std;
int n,st,ed,dis[N],head[N],tot;
bool vis[N];
queue<int> q;
struct edge{
int nxt,to,w;
}e[N];
inline int read(){
int x=0,f=0;
char ch=getchar();
while(!isdigit(ch)) f|=(ch=='-'),ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return f?-x:x;
}
inline void init(int s){
for(int i=1;i<=n;i++){
dis[i]=inf;
vis[i]=false;
}
dis[s]=0;
q.push(s);
}
inline void add(int u,int v,int w){
e[++tot].to=v;
e[tot].nxt=head[u];
e[tot].w=w;
head[u]=tot;
}
inline void spfa(int s){
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=false;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(dis[v]>dis[u]+e[i].w){
dis[v]=dis[u]+e[i].w;
if(!vis[v]){
vis[v]=true;
q.push(v);
}
}
}
}
}
int main(){
n=read();
for(int i=1,u,v,w;i<n;i++){
u=read();v=read();
add(u,v,2);
add(v,u,3);
}
st=read(),ed=read();
init(st);
spfa(st);
printf("%d",dis[ed]);
return 0;
}
7-3 修轻轨 (100 分)
代码长度限制 \(16 KB\)
时间限制 \(500 ms\)
内存限制 \(10 MB\)
Description
长春市有 \(n\) 个交通枢纽,计划在 \(1\) 号枢纽到 \(n\) 号枢纽之间修建一条轻轨。轻轨由多段隧道组成,候选隧道有 \(m\) 段。每段候选隧道只能由一个公司施工,施工天数对各家公司一致。有 \(n\) 家施工公司,每家公司同时最多只能修建一条候选隧道。所有公司可以同时开始施工。请评估:修建这条轻轨最少要多少天。
Input
第 \(1\) 行,两个整数 \(n\) 和 \(m\),用空格分隔,分别表示交通枢纽的数量和候选隧道的数量,\(1 ≤ n ≤ 100000\),\(1 ≤ m ≤ 200000\)。
第 \(2\) 行到第 \(m+1\) 行,每行三个整数 \(a\)、\(b\)、\(c\),用空格分隔,表示枢纽 \(a\) 和枢纽 \(b\) 之间可以修建一条双向隧道,施工时间为 \(c\) 天,\(1 ≤ a, b ≤ n,1 ≤ c ≤ 1000000\)。
Output
输出一行,包含一个整数,表示最少施工天数。
Sample Input
6 6
1 2 4
2 3 4
3 6 7
1 4 2
4 5 5
5 6 6
Sample Output
6
思路
题目中说求从点 \(1\) 到点 \(n\) 的最短距离,为使所用到的边的边权尽可能小,我们可以使用Kluskal,但程序的终止条件不是建成一棵树,而是点 \(1\) 和点 \(n\) 联通时终止,该边边权即为通道中最大边权。
代码
#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<queue>
#define N 400010
using namespace std;
struct edge{
int u,v,w;
} e[N];
int n,m,ans,f[N],cnt;
inline int read(){
int f=0,x=0;
char ch=getchar();
while(!isdigit(ch)) f|=(ch=='-'),ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return f?-x:x;
}
inline bool cmp(edge a,edge b){
return a.w<b.w;
}
inline int find(int v){
return f[v]==v?v:f[v]=find(f[v]);
}
inline bool merge(int x,int y){
int t1=find(x);
int t2=find(y);
if(t1!=t2){
f[t2]=t1;
return true;
}
return false;
}
int main(){
n=read();
m=read();
for(int i=1; i<=n; i++) f[i]=i;
for(int i=1; i<=m; i++){
e[i].u=read();
e[i].v=read();
e[i].w=read();
}
sort(e+1,e+m+1,cmp);
for(int i=1; i<=m; i++){
if(merge(e[i].u,e[i].v)){
ans=max(ans,e[i].w);
}
if(find(1) == find(n)) break;
}
printf("%d",ans);
return 0;
}
7-4 数据结构设计I (100 分)
代码长度限制 \(16 KB\)
时间限制 \(500 ms\)
内存限制 \(50 MB\)
Description
小唐正在学习数据结构。他尝试应用数据结构理论处理数据。最近,他接到一个任务,要求维护一个动态数据表,并支持如下操作:
插入操作(I):从表的一端插入一个整数。
删除操作(D):从表的另一端删除一个整数。
取反操作(R):把当前表中的所有整数都变成相反数。
取最大值操作(M):取当前表中的最大值。
如何高效实现这个动态数据结构呢?
Input
第 \(1\) 行,包含 \(1\) 个整数 \(M\),代表操作的个数, \(2≤M≤1000000\)。
第 \(2\) 到 \(M+1\) 行,每行包含 \(1\) 个操作。每个操作以一个字符开头,可以是 \(I\)、\(D\)、\(R\)、\(M\)。如果是I操作,格式如下:\(I\) \(x\), \(x\) 代表插入的整数,\(-10000000≤x≤10000000\)。
Output
若干行,每行 \(1\) 个整数,对应M操作的返回值。如果 \(M\) 和 \(D\) 操作时队列为空,忽略对应操作。
Sample Input
6
I 6
R
I 2
M
D
M
Sample Output
2
2
思路
观察数据范围,考虑 \(O(m)\) 做法,但这题数据出的比较水,像一些线段树、multiset这种 \(O(mlogm)\) 的做法也就过了,但是当时只想着 \(O(m)\) 做法了,于是便写出了全场唯一一个单调队列。
具体做法如下:建两个单调队列 \(q1,q2\),分别记录反转前的最大值和反转后的最大值,并记录一个反转标记,当标记了反转时将读入的数放入 \(q2\),将其相反数放入 \(q1\),反之同理,其余三个操作不再赘述。
代码
#include<cstdio>
#include<cctype>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define rg register
#define ll long long
using namespace std;
inline int read(){
rg int f = 0, x = 0;
rg char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while( isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
return f? -x: x;
}
int n, m;
int a1[1000010], q1[1000010], a2[1000010], q2[1000010];
int f1 = 1, r1 = 1, f2 = 1, r2 = 1;
int rev = 0, cnt = 0;
const int inf = 0x7fffffff;
signed main(){
m = read();
q1[0] = 0, a1[0] = -inf, a2[0] = +inf, q2[0] = 0;
for(rg int i = 1; i <= m; ++i){
char ch[1];
scanf("%s", ch);
switch(ch[0]){
case 'D' :{
if(cnt < n){
cnt ++;
while(f1 < r1 && q1[f1] <= cnt) f1 ++;
while(f2 < r2 && q2[f2] <= cnt) f2 ++;
}
getchar();
break;
}
case 'I' :{
if(rev){
a2[++n] = read();
a1[n] = -a2[n];
} else{
a1[++n] = read();
a2[n] = -a1[n];
}
while(f1 < r1 && a1[q1[r1 - 1]] < a1[n]) r1 --;
q1[r1 ++] = n;
while(f2 < r2 && a2[q2[r2 - 1]] < a2[n]) r2 --;
q2[r2 ++] = n;
break;
}
case 'R' :{
rev = (rev + 1) % 2;
getchar();
break;
}
case 'M' :{
if(cnt < n){
if(rev) printf("%d\n", a2[q2[f2]]);
else printf("%d\n", a1[q1[f1]]);
}
getchar();
break;
}
}
}
return 0;
}