数据结构+贪心专题
数据结构
树
这是二叉搜索树吗?
题目链接
解题思路:
此题我们知道了二叉搜索树的性质:左子树小于根,右子树大于等于根。
且输入的是前序遍历,则对一个二叉树[l,r]:a[l]是根,[l+1,r]是左右子树范围。
其中,前x项若都小于根,剩下的都大于等于根:则从l+1开始的前x个就是左子树,剩下的都是右子树。如此就分出了左右子树[l1,r1][l2,r2],然后再对左右子树递归即可。
由于输出要后序遍历,则我们只需:递归左子树,递归右子树,存根 (按照后序遍历的顺序)即可。
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
const int N = 10010;
int n, flag;
int t[N];
vector<int>ans;
void solve(int l, int r) {
if (r < l)
return ;
int i = l + 1, j = r;
if (!flag) {
while (i <= r && t[i] < t[l]) // 最后停止在大于等于的位置,比正确位置后移了一位
i++;
while (j > l && t[j] >= t[l]) // 最后停在小于的位置,比正确位置前移了一位
j--;
if (i - j != 1) // 只有当两者相差1也就是中间没有元素的时候才正确
return ;
solve(l + 1, i - 1);
solve(j + 1, r);
ans.push_back(t[l]);
} else {
while (i <= r && t[i] >= t[l])
i++;
while (j > l && t[j] < t[l])
j--;
if (i - j != 1)
return ;
solve(l + 1, i - 1);
solve(j + 1, r);
ans.push_back(t[l]);
}
}
int main() {
int f = 0;
cin >> n;
for (int i = 1 ; i <= n ; ++i)
cin >> t[i];
solve(1, n);
if (ans.size() == n) {
puts("YES");
for (auto x : ans) {
if (f)
cout << " ";
cout << x;
f++;
}
} else {
ans.clear();
flag = 1;
solve(1, n);
if (ans.size() == n) {
puts("YES");
for (auto x : ans) {
if (f)
cout << " ";
cout << x, f++;
}
} else
puts("NO");
}
return 0;
}
树的遍历
题目链接
解题思路:
思路:(1)通过后序遍历找到根结点,再通过根结点在中序遍历的位置找出左子树、右子树。(2)根据左子树在后序遍历结果的顺序,找到左子树的根结点,视左子树为一棵独立的树,转步骤(1)。(3)根据右子树在后序遍历结果的顺序,找到右子树的根结点,视右子树为一棵独立的树,转步骤(1)。
注意点:
函数参数需要一个创建二叉树的指针 ,和后序的左右指针,以及中序的左右指针,5个参数。
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <cstdlib>
using namespace std;
const int N = 1010;
int n ;
int in[N], nx[N];
typedef struct node {
int val;
struct node *l, *r;
} BitNode, *BiTree;
void build(BiTree &T, int L1, int R1, int L2, int R2) {
T = (BiTree)malloc(sizeof (BitNode));
T->val = nx[R1];
int in_root ;
for (int i = L2 ; i <= R2 ; ++i)
if (in[i] == nx[R1]) {
in_root = i ;
break;
}
if ( in_root - L2 != 0 ) {
build(T->l, L1, L1 + in_root - L2 - 1, L2, in_root - 1);
} else
T->l = NULL;
if (R2 - in_root != 0) {
build(T->r, R1 - (R2 - in_root), R1 - 1, in_root + 1, R2 );
} else
T->r = NULL;
}
void bfs(BiTree T) {
queue<BiTree>q;
q.push(T);
int f = 0;
while (q.size()) {
auto u = q.front();
q.pop();
if (f)
cout << " ";
cout << u->val;
f++;
if (u->l)
q.push(u->l);
if (u->r)
q.push(u ->r);
}
}
int main() {
BiTree t;
cin >> n;
for (int i = 1 ; i <= n ; ++i)
cin >> nx[i];
for (int j = 1; j <= n ; ++j)
cin >> in[j];
build(t, 1, n, 1, n);
bfs(t);
return 0;
}
玩转二叉树(中序+前序建树 + 翻转输出)
题目链接
解题思路:
题目的要求很清楚了,就是通过前序序列和中序序列将树建立出来。至于后面的镜面反转输出,我们在用BFS输出的时候,原本我们是先左子树再右子树。那么我们只需改变顺序,变成先右子树,再左子树就满足要求了。
代码:
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <queue>
using namespace std;
const int N = 50;
int n, in[N], pre[N];
typedef struct node {
int val;
node *l, *r;
} TNode, * Tree;
void build(Tree &T, int L1, int R1, int L2, int R2) {
T = (Tree)malloc(sizeof (TNode));
T->val = pre[L1];
int root;
for (int i = L2 ; i <= R2 ; ++i)
if (in[i] == pre[L1]) {
root = i;
break;
}
if (root - L2 != 0) {
build(T->l, L1 + 1, L1 + root - L2, L2, root - 1 );
} else
T->l = NULL;
if (R2 - root != 0) {
build(T->r, R1 - (R2 - root) + 1, R1, root + 1, R2);
} else
T->r = NULL;
}
void bfs(Tree T) {
queue<Tree>q;
q.push(T);
int f = 0;
while (q.size()) {
auto u = q.front();
q.pop();
if (f)
cout << " ";
cout << u->val;
f++;
if (u->r)
q.push(u->r);
if (u->l)
q.push(u->l);
}
}
int main() {
Tree t;
cin >> n;
for (int i = 1; i <= n ; ++i)
cin >> in[i];
for (int j = 1; j <= n ; ++j)
cin >> pre[j];
build(t, 1, n, 1, n);
bfs(t);
return 0;
}
并查集
排座位
题目链接
解题思路:
由于题目说了只要朋友关系具有传递性。因此我们只对朋友关系利用并查集维护。至于敌人关系我们就可以用一个邻接矩阵来存。如果是敌人就是1.
代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int g[N][N];
int f[N], n, m, k;
int find(int x) {
if (x == f[x])
return x;
else
return f[x] = find(f[x]);
}
void join(int x, int y) {
x = find(x), y = find(y);
if (x != y)
f[x] = y;
}
int main() {
cin >> n >> m >> k;
for (int i = 1; i <= n ; ++i)
f[i] = i;
while (m--) {
int a, b, c;
cin >> a >> b >> c;
if (c == 1)
join(a, b);
else
g[a][b] = g[b][a] = c;
}
while (k--) {
int a, b;
cin >> a >> b;
if (find(a) == find(b) && g[a][b] == 0)
cout << "No problem\n";
else if (g[a][b] == 0)
cout << "OK\n";
else if (find(a) == find(b) && g[a][b] == -1)
cout << "OK but...\n";
else
cout << "No way\n";
}
return 0;
}
家庭房产
题目链接
解题思路:
- 关键点1是如何在维护家庭关系的同时记录最小的值:由于并查集中的祖先其实就是等价类中的代表元素,因此我们只需要在合并的时候,一直令最小的值来充当代表元素即可。
- 对于数据的数理:起初我们用一个结构体记录下一个人的父母,房产,面积等信息。同时将这个值以及父母的值记录下来。之后,我们用另一个结构体来存下每一个家庭的值(即题目所要求的信息)我们遍历所有的值。如果标记过那么找到其代表元素,同时将代表元素标记上。这样在遍历之后,一个家庭所要求的信息都存在了代表元素上。那么我们对其进行排序后输出(由于题目是数值有大到下,因此有数值肯定比没有数值的大,因此排序完成之后有价值的值都放到了前边)即可。
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <map>
using namespace std;
const int N = 10100;
int n ;
int f[N], cnt;
bool st[N];
struct node {
int f, m ;
int child[10];
int t, sum;
} p[N];
int find(int x) {
if (f[x] == x)
return x;
return f[x] = find(f[x]);
}
void join(int x, int y) {
x = find(x), y = find(y);
if (x != y) {
if (y < x)
f[x] = y;
else
f[y] = x;
}
}
struct Date {
int flag, cnt, id ;
double avg, sum;
bool operator < ( Date w) {
if (sum == w.sum)
return id < w.id;
else
return sum > w.sum;
}
} sg[N];
bool cmp(Date a, Date b) {
if (a.sum != b.sum)
return a.sum > b.sum;
else
return a.id < b.id;
}
int main() {
cin >> n;
for (int i = 1 ; i < N ; ++i)
f[i] = i;
for (int i = 1; i <= n ; ++i) {
int a, b, c, k;
cin >> a ;
st[a] = 1;
cin >> p[a].f >> p[a].m >> k;
if (p[a].f != -1)
join(a, p[a].f), st[p[a].f] = 1;
if (p[a].m != -1)
join(a, p[a].m), st[p[a].m ] = 1;
for (int j = 0 ; j < k ; ++j) {
int x;
cin >> p[a].child[j];
join(a, p[a].child[j]);
st[p[a].child[j]] = 1;
}
cin >> p[a].t >> p[a].sum;
}
for (int i = 0 ; i < N ; ++i)
if (st[i]) {
int pa = find(i);
sg[pa].flag = 1;
sg[pa].id = pa;
sg[pa].cnt ++;
sg[pa].avg += p[i].t;
sg[pa].sum += p[i].sum;
}
for (int i = 0 ; i < N ; ++i)
if (sg[i].flag) {
sg[i].avg = sg[i].avg * 1.0 / sg[i].cnt;
sg[i].sum = sg[i].sum * 1.0 / sg[i].cnt;
}
sort(sg, sg + N - 1 );
int idx = 0;
for (int i = 0 ; sg[i].flag ; ++i)
idx++;
cout << idx << endl;
for (int i = 0 ; sg[i].flag ; ++i)
printf("%04d %d %.3lf %.3lf\n", sg[i].id, sg[i].cnt, sg[i].avg, sg[i].sum);
return 0;
}
部落
题目链接
解题思路
对于一个圈子,我们选出一个人,令其是是圈子的根,同时利用一个bool数组记录出现的值。在对每一个圈子都操作完之后,我们遍历1~N的所有数,如果记录中就加一,同时如果它直接就是根则互不相交的部落的个数加1.
代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e4 + 10;
int n, m ;
int f[N];
bool st[N];
int find(int x) {
if (x == f[x])
return x;
else
return f[x] = find(f[x]);
}
void join(int x, int y) {
x = find(x), y = find(y);
if (x != y)
f[x] = y;
}
int main() {
cin >> n;
for (int i = 1 ; i < N ; ++i)
f[i] = i;
while (n--) {
int k;
cin >> k;
int f = 0;
while (k--) {
int x;
cin >> x;
st[x] = 1;
if (!f)
f = x;
else
join(x, f);
}
}
int cnt = 0, num = 0;
for (int i = 1 ; i < N ; ++i) {
if (st[i])
num++;
if (st[i] && f[i] == i)
cnt++;
}
cout << num << " " << cnt << endl;
cin >> m;
while (m--) {
int x, y;
cin >> x >> y;
if (find(x) == find(y))
cout << "Y" << endl;
else
cout << "N\n";
}
return 0;
}
线性结构
链表
重排链表
题目链接
解题思路:
对于这里的地址节点,直接当成字符串处理就好多了。利用map存一个节点的前后节点。重派的操作分为奇偶来进行曹组。t一直是跟再head的后边,并连上head。而head的情况则要根据奇偶次数来判断是往前还是往后。如图
注意事项:
有的测试数据中会包含错误结点,这些结点不会被用上。
代码:
#include <iostream>
#include <algorithm>
#include <string>
#include <map>
using namespace std;
int n, cnt ;
string rt, trail;
map<string, string>pre, ne;
map<string, int > ans;
void get(string h) {
while (h != "-1") {
h = ne[h];
cnt++;
}
}
int main() {
cin >> rt >> n;
for (int i = 1 ; i <= n ; ++i) {
string s1, s2;
int x;
cin >> s1 >> x >> s2;
if (s2 == "-1")
trail = s1;
ans[s1] = x ;
ne[s1] = s2;
pre[s2] = s1;
}
get(rt); // 由于有的节点没用,不在链表中因此需要先求出真实的长度
string head = rt, t = trail, tp, nx ;
int f = 0;
while (1) {
tp = t , nx = ne[t];
ne[t] = head;
t = head;
f++;
if (f & 1) // 奇数
head = pre[tp];
else
head = nx;
if (f == cnt)
{
ne[t] = "-1";
break;
}
}
string s = trail;
for (int i = 1 ; i <= n ; ++i) {
cout << s << " " << ans[s] << " " << ne[s] << endl;
s = ne[s];
if(s == "-1")break;
}
return 0;
}