【JLU数据结构荣誉课】第三次上机实验
->点我进原题
-> 7-1 二叉树最长路径
-> 7-2 森林的层次遍历
-> 7-3 纸带切割
-> 7-4 序列乘积
7-1 二叉树最长路径 (100 分)
代码长度限制 \(16 KB\)
时间限制 \(100 ms\)
内存限制 \(5 MB\)
Description
给定一棵二叉树 \(T\),求 \(T\) 中的最长路径的长度,并输出此路径上各结点的值。若有多条最长路径,输出最右侧的那条。
Input
第1行,\(1\) 个整数 \(n\),表示二叉树有 \(n\) 个结点, \(1≤n≤100000\).
第2行,\(2n+1\) 个整数,用空格分隔,表示T的扩展先根序列, \(-1\) 表示空指针,结点用编号 \(1\) 到 \(n\) 表示。
Output
第1行,\(1\) 个整数 \(length\),\(length\) 表示 \(T\) 中的最长路径的长度。
第2行,\(length+1\) 个整数,用空格分隔,表示最右侧的最长路径。
Sample Input
5
1 2 -1 -1 3 4 -1 -1 5 -1 -1
Sample Output
2
1 3 5
思路
先按照扩展先根序列建树,执行根左右的顺序,遇到-1就返回。对于求其最长路径,直接dfs暴力求解即可,在长度相等时也更新一下,以保证求得的是最右最长序列。
代码
#include<cstdio>
#include<cctype>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<map>
#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;
}
struct tree{
int ls, rs;
}t[100010];
int n, root, maxn = 0, ans[100010], temp[100010];
inline int build(){
int tmp = read();
if(tmp == -1) return 0;
else{
t[tmp].ls = build();
t[tmp].rs = build();
}
return tmp;
}
inline void dfs(rg int u, rg int tot){
int lson = t[u].ls, rson = t[u].rs;
if(!lson && !rson){
if(tot >= maxn){
maxn = tot;
for(rg int i = 0; i <= maxn; ++i) ans[i] = temp[i];
} else return ;
}
if(lson){
temp[++tot] = lson;
dfs(lson, tot);
tot --;
}
if(rson){
temp[++tot] = rson;
dfs(rson, tot);
tot --;
}
}
signed main(){
n = read();
for(rg int i = 1; i <= n; ++i) t[i].ls = t[i].rs = 0;
root = build();
dfs(root, 0);
ans[0] = root;
printf("%d\n", maxn);
for(rg int i = 0; i <= maxn; ++i){
printf("%d", ans[i]);
if(i != maxn) printf(" ");
}
return 0;
}
/*
6
1 2 -1 -1 3 4 -1 6 -1 -1 5 -1 -1
*/
7-2 森林的层次遍历 (100 分)
代码长度限制 \(16 KB\)
时间限制 \(100 ms\)
内存限制 \(5 MB\)
Description
给定一个森林 \(F\),求 \(F\) 的层次遍历序列。森林由其先根序列及序列中每个结点的度给出。
Input
第1行,1个整数 \(n\),表示森林的结点个数, \(1≤n≤100000\).
第2行,\(n\) 个字符,用空格分隔,表示森林 \(F\) 的先根序列。字符为大小写字母及数字。
第3行,\(n\) 个整数,用空格分隔,表示森林 \(F\) 的先根序列中每个结点对应的度。
Output
1行,\(n\) 个字符,用空格分隔,表示森林 \(F\) 的层次遍历序列。
Sample Input
14
A B C D E F G H I J K L M N
4 0 3 0 0 0 0 2 2 0 0 0 1 0
Sample Output
A M B C G H N D E F I L J K
思路
题目中给出了度数和先根序列,所以我们建树时,每个节点的度数则是其子节点的个数,由于是先根序列,所以每次对于度数非0的节点,要递归找其子节点、其子节点的节点等等(见build()函数)。起初思路是用非vector版本的邻接表建边,将根节点存下来,再bfs将每次队列中的节点的子节点入队并输出得解,但由于非vector的邻接表在存图时会反着存,所以在层次遍历时要再反过来,栈溢出导致段错误;同时由于边数的不确定也导致了MLE。最后改成用vector存图就AC了。
代码
#include<cstdio>
#include<cctype>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<map>
#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 deg[100010];
int n;
char ch[100010][2];
vector<int> e[100010];
int tot = 0;
int cnt = 0;
queue<int> q;
inline int build(){
int tmp = ++ cnt;
for(rg int i = 1; i <= deg[tmp]; ++i){
e[tmp].push_back(build());
}
return tmp;
}
signed main(){
n = read();
for(rg int i = 0; i < n; ++i) scanf("%s", ch[i]);
for(rg int i = 1; i <= n; ++i) deg[i] = read();
while(cnt < n){
int tmp = build();
q.push(tmp);
}
while(!q.empty()){
int tmp = q.front();
q.pop();
cout << ch[tmp - 1];
for(rg int i = 0; i < deg[tmp]; ++i){
q.push(e[tmp][i]);
}
if(!q.empty()) putchar(' ');
}
return 0;
}
7-3 纸带切割 (100 分)
代码长度限制 \(16 KB\)
时间限制 \(100 ms\)
内存限制 \(5 MB\)
Description
有一条细长的纸带,长度为 \(L\) 个单位,宽度为一个单位。现在要将纸带切割成 \(n\) 段。每次切割把当前纸带分成两段,切割位置都在整数单位上,切割代价是当前切割纸带的总长度。每次切割都选择未达最终要求的最长纸带切割,若这样的纸带有多条,则任选一条切割。如何切割,才能完成任务,并且总代价最小。
Input
第1行,\(1\) 个整数 \(n\),表示切割成的段数, \(1≤n≤100000\).
第2行,\(n\) 个整数 \(Li\),用空格分隔,表示要切割成的各段的长度,\(1≤Li≤200000000\),\(1≤i≤n\).
Output
第1行,1个整数,表示最小的总代价。
第2行,若干个整数,用空格分隔,表示总代价最小时每次切割的代价。
Sample Input
5
5 6 7 2 4
Sample Output
54
24 13 11 6
思路
题目说切割纸带,但我们可以反过来将切好的纸带合并成原来的纸带,于是就变成了合并果子(bushi。维护一个小根堆,将所有纸带都放进去,每次取最短的两条合并再放进去,直到小根堆里只剩一条纸带即可。
代码
#include<cstdio>
#include<cctype>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<map>
#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, a[100001];
ll ans[100001];
ll maxn = 0;
int tot = 0;
priority_queue <ll, vector<ll>, greater<ll> > q;
signed main(){
n = read();
for(rg int i = 1; i <= n; ++i){
a[i] = read();
q.push(a[i]);
}
int u, v;
while(!q.empty()){
u = q.top();
q.pop();
if(q.empty()) break;
v = q.top();
q.pop();
q.push(u + v);
maxn += (u + v);
ans[++ tot] = u + v;
}
printf("%lld\n", maxn);
for(rg int i = tot; i >= 1; --i){
printf("%lld", ans[i]);
if(i != 1) printf(" ");
}
return 0;
}
7-4 序列乘积 (100 分)
代码长度限制 \(16 KB\)
时间限制 \(100 ms\)
内存限制 \(5 MB\)
Description
两个递增序列 \(A\) 和 \(B\) ,长度都是 \(n\)。令 \(Ai\) 和 \(Bj\) 做乘积,\(1≤i,j≤n\).请输出 \(n\times n\) 个乘积中从小到大的前 \(n\) 个。
Input
第1行,\(1\) 个整数 \(n\) ,表示序列的长度, \(1≤n≤100000\).
第2行,\(n\) 个整数 \(Ai\),用空格分隔,表示序列 \(A\),\(1≤Ai≤40000\),\(1≤i≤n\).
第3行,\(n\) 个整数 \(Bi\),用空格分隔,表示序列 \(B\),\(1≤Bi≤40000\),\(1≤i≤n\).
Output
1行,\(n\) 个整数,用空格分隔,表示序列乘积中的从小到大前 \(n\) 个。
Sample Input
5
1 3 5 7 9
2 4 6 8 10
Sample Output
2 4 6 6 8
思路
求矩阵前 \(n\) 小项问题,看时间限制明显 \(O(n)\) 会TLE,所以考虑别的方法。对于矩阵 \(Sij\) 表示 \(Ai\times Bj\) ,题目上说 \(A\) 和 \(B\) 序列是递增的,所以较小项只能出现在左侧而不是右侧,可以考虑维护一个小根堆,先把 \(A1\times Bi\) 这 \(n\) 个值放入小根堆,此时堆内存在所有 \(B\) 序列的值,再每次取堆首,并将所取元素在 \(A\) 序列中的值向后移动一位与所对应 \(B\) 序列中的值相乘后放入小根堆,这样就能保证每次小根堆中只有 \(n\) 个元素,以达到 \(O(nlogn)\) 的复杂度,AC!
代码
#include<cstdio>
#include<cctype>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<map>
#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;
}
struct node{
int x, y;
ll val;
node(int x, int y, ll val) :x(x), y(y), val(val){}
bool operator <(const node& a) const{
return val > a.val;
}
};
priority_queue<node> q;
int n, a[100001], b[100001], tot = 0;
signed main(){
n = read();
for(rg int i = 1; i <= n; ++i) a[i] = read();
for(rg int i = 1; i <= n; ++i) b[i] = read();
for(rg int i = 1; i <= n; ++i) q.push(node(1, i, a[1] * b[i]));
while(tot < n){
node tmp = q.top();
tot ++;
printf("%lld", tmp.val);
if(tot != n) putchar(' ');
q.pop();
if(tmp.x < n) q.push(node(tmp.x + 1, tmp.y, a[tmp.x + 1] * b[tmp.y]));
else q.push(node(tmp.x, tmp.y + 1, a[tmp.x] * b[tmp.y]));
}
return 0;
}