题目链接:ZOJ-3963.
题目描述
给出一个长度为 n 的整数序列 { S1, S2, … , Sn} ,用这些数组成二叉树,要求满足:父亲节点的值大于等于其儿子节点的值,且下标小于儿子节点的下标。
问:放下所有的点,最少需要多少棵二叉树。
思路
-
错误思路
-
找下降子序列,认为只有找到比当前所有二叉树头结点最小的点时,才需要增加一课二叉树。
反例: 1 7 8 9 4 5 6 2 3
错误点:忽略了下标因素
正确思路
- 从前往后依次遍历每个点,对于每个待插入的点 x ,在它之前的所有节点中,找到比它小且最大的那一个点 fx ,且 fx 没有儿子节点或只有一个儿子节点,将 x 放在 fx 儿子节点的位置。如果没有满足的 fx,则以 x 为头结点新建一棵二叉树。
- 具体实现:用 set 存放:已经连接至二叉树上,出度小于2(少于两个儿子节点)的点。对于每个待插入的点,若能找到 fx,将 x 放入 fx 的二叉树中,更新 fx 的出度,如果出度等于2,从set中删除该点。点的值可能会重复,可记录set中本应该有的该点的次数。
代码
#include <bits/stdc++.h>
using namespace std;
#define MAXN 1000005
set<int>s;
vector<int>v[MAXN];//记录每个二叉树
int c[MAXN], Out[MAXN], to[MAXN];//Out[x]记录 x 的出度,to[x]表示 x 所属二叉树
int main() {
set<int>::iterator it;
int T, n, x;
scanf("%d", &T);
while(T--) {
int num = 0;
scanf("%d", &n);
s.clear();
for(int i = 1; i <= n; i++) {
c[i] = 0;
Out[i] = 0;
to[i] = i;
v[i].clear();
}
for(int i = 1; i <= n; i++) {
scanf("%d", &x);
if(i == 1) {
num = 1;
s.insert(x);
c[x]++;
v[num].push_back(i);
to[x] = num;
}
else {
it = s.upper_bound(x);
if(it == s.begin()) {
num++;
v[num].push_back(i);
s.insert(x);
c[x]++;
to[x] = num;
}
else{
it--;
int fx = *it;
Out[fx]++;
if(Out[fx] == 2) {
if(c[fx] > 1) {
c[fx]--;
Out[fx] = 0;
}
else {
s.erase(fx);
c[fx] = 0;
Out[fx] = 0; // WA
}
}
if(!s.count(x)) {
s.insert(x);
}
c[x]++;
to[x] = to[fx];
v[ to[fx] ].push_back(i);
}
}
}
printf("%d\n", num);
for(int i = 1; i <= num; i++) {
int z = v[i].size();
printf("%d ",z);
for(int j = 0; j < z; j++) {
printf("%d", v[i][j]);
if(j != z - 1) printf(" ");
}
printf("\n");
}
}
return 0;
}
错误点
在删除出度为2的节点时,没有将其出度清零,导致后续出度为3。