[原创]玲珑OJ 1098 && Round #11 C 萌萌哒的第三题 [并查集+思维]【数据结构】

2017-03-14 12:37:34 Tabris_ 阅读数:704


博客爬取于2020-06-14 22:41:13
以下为正文

版权声明:本文为Tabris原创文章,未经博主允许不得私自转载。
https://blog.csdn.net/qq_33184171/article/details/62040995


题目链接:http://www.ifrog.cc/acm/problem/1098
————————————————————————————————————————————
DESCRIPTION

按顺序把一个1~n的排列插入一棵原来是空的排序二叉树,求所有节点的高度的和以及积(其中跟节点的高度算为1),这个结果可能有点大,所以你只需要输出和以及积对10^9+7求与的结果。

INPUT
输入数据第一行一个整数T(<=15)表示数据组数。
接下来每组数据一行四个整数,n(1<=n<=1000000)、a、b、c(1<=a,b,c<=10^9),然后利用下面一段程序生成插入排序二叉树的排列。

(注意:此处应有代码,两段代码放于描述图中,请仔细观看,代码贴于下方网站)
http://paste.ubuntu.com/24106406/

运行make_data得到的num数组就是插入的排列。

OUTPUT

每组数据输出一行两个数,分别表示所有节点的高度之和对10^9+7求余以及高度之积对10^9+7求余的结果。

SAMPLE INPUT
2
3 4 5 6
4 1 5 9
SAMPLE OUTPUT
6 6
8 12
HINT
提示:两个样例生成的排列分别为
3 1 2
2 3 1 4
————————————————————————————————————————————
解题思路:

其实这题很容易就想到了O(T×n×logn)O(T\times n\times \log {n})的做法,

我们只要用一个树状数组维护,再加上一个二分,
每次将一个元素插入到树中,那么对于待插得元素,它一定是在树中元素中比它大的最小元素或比它小的最大元素的下面,并且一定是其中深度大的那个点的孩子,

那么我们很容易用二分和树状数组维护处待插元素的父节点,

但是交上去T了 ,然后看群里讨论,说可以去掉log\log

先附上O(T×n×logn)O(T\times n\times \log {n})的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
int n,a,b,c;

int sum[N];
# define lowbit(x) (x&(-x))
void update(int index,int val){
for(int i=index;i<=n;i+=lowbit(i))sum[i]+=val;
}
int getSum(int index){
int ans = 0;
for(int i=index;i>0;i-=lowbit(i))ans+=sum[i];
return ans;
}

int Bsearch(int x){
int l=0,r=n,ans=0,mid;
while(l<=r){
mid=(l+r)>>1;
if(getSum(mid)>=x){
ans = mid;
r=mid-1;
}
else l=mid+1;
}
return ans;
}

int h[N],ind[N];
int main(){
int _;
scanf("%d",&_);
while(_--){

scanf("%d%d%d%d",&n,&a,&b,&c);
make_data(n,a,b,c);
for(int i=0;i<=n;i++)sum[i]=0,ind[i]=0,h[i]=0;
//for(int i=0;i<n;i++)printf("%d ",num[i]);puts("");

h[0]=1;update(num[0],1);ind[num[0]]=0;

LL ji=1,he=0;
for(int i=1,pre,id1,id2;i<n;i++){
pre=getSum(num[i]);
update(num[i],1);
ind[num[i]]=i;
// printf("%d ",pre);

id1 = Bsearch(pre);
id2 = Bsearch(pre+1);

// printf("%d %d ",id1,id2);

if(id1==0){
id1 = ind[id1];
h[i]=h[id1]+1;
continue;
}

if(id2==0){
id2 = ind[id2];
h[i]=h[id2]+1;
continue;
}

id1 = ind[id1];
id2 = ind[id2];
// printf("%d %d\n",id1,id2);
h[i]=(h[id1]>h[id2]?h[id1]:h[id2])+1;


}

for(int i=0;i<n;i++){
//printf("%d ",h[i]);
he+=h[i];
he = (he>MOD)?(he-MOD):he;
ji*=h[i];
ji%=MOD;
}
//puts("");
printf("%lld %lld\n",he,ji);
}
return 0;
}

然后冥思苦想 ,有意识的倒过来插元素,但却不知道怎么维护, 知道比赛后看到题解说用并查集维护

官方题解:

第三题、当一课排序二叉树建立起来之后我们可以发现,从某个点x往上走,第一个往左的节点的值是在x前插入的小于x的最大值,第一个往右的值是在x前插入的大于x的最小值,所以x所在的层数就是在插入x之前比x大的最小值的层数和比x小的最大值的层数中,较大值加一,若把问题反过来看,不是插入而是从二叉树中把一个个点拿走,那么可以很简单地用并查集找到上述的两个值。

但是本菜真的想不出如何用并查集进行维护,

直到今天向格格姐姐要来了标程…

虽然还不是特别懂,但也感受到了这个姿势的妙<

正常的并查集仅仅是单一的维护父节点,再简单点就是堆与堆之间的关系,
这个则不同,不仅开了两个并查集维护左边和右边的关系,
同事每个并查集都用了一个pair,来维护
一边维护左边的,一边维护了右边的关系,使得确保我在记录每个节点的左父节点和右父节点(大概可以这么理解)的关系时能做到O(1)O(1).

附本题代码(我写的太丑了 还是贴标程吧)
————————————————————————————————————————————

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <math.h>
# include <assert.h>
# include <vector>
# include <string>
# include <queue>
# include <map>
# include <set>
# include <iostream>
# include <algorithm>
# include <stack>

using namespace std;

# define foreach(it, s) for(__typedef(s.begin()) it = s.begin();it != s.end();it++)
# define sgn(x) ((x)<-eps?-1:(x)>eps)
typedef long long LL;
typedef pair<int, int> pii;


const int maxn = 1000000 + 10;
int n, num[maxn];
int ans[maxn];
pii fal[maxn], far[maxn];
int Left[maxn], Right[maxn];
const int Mod = 1000000000 + 7;

void rd(int Left, int Right, int a, int b, int c) {
int len = Right - Left;
long long rnum = b;
for (int i=Left;i<Right;i++) {
swap(num[i], num[i + rnum % len]);
rnum = (rnum * a + b) % c;
len--;
}
}

void make_data(int n, int a, int b, int c) {
for (int i=0;i<n;i++) num[i] = i + 1;
int Left = 0, Right = min(110, n);
for (;;) {
rd(Left, Right, a, b, c);
Left += 100;
Right += 100;
if (Left >= n) break;
if (Right >= n) Right = n;
}
}

int getfal(int r) {
if (fal[r].first < 0) return r;
return fal[r].first = getfal(fal[r].first);
}
int getfar(int r) {
if (far[r].first < 0) return r;
return far[r].first = getfar(far[r].first);
}

int main() {
int T;
int cas = 0;
scanf("%d", &T);
while (T--) {
int a, b, c;
scanf("%d%d%d%d", &n, &a, &b, &c);
make_data(n, a, b, c);
for (int i=0;i<=n+1;i++) {
fal[i] = pii(-1, i);
far[i] = pii(-1, i);
ans[i] = -1;
Left[i] = Right[i] = 0;
}
int fl, fr;
for (int j=n-1;j>=0;j--) {
int i = num[j];
Left[i] = fal[getfal(i - 1)].second;
Right[i] = far[getfar(i + 1)].second;

fl = getfal(i - 1);
fr = getfal(i);
if (fal[fl].first > fal[fr].first) swap(fl, fr);
fal[fl].first += fal[fr].first;
fal[fr].first = fl;
fal[fl].second = min(fal[fl].second, fal[fr].second);

fl = getfar(i);
fr = getfar(i + 1);
if (far[fl].first > far[fr].first) swap(fl, fr);
far[fl].first += far[fr].first;
far[fr].first = fl;
far[fl].second = max(far[fl].second, far[fr].second);
}
LL Y = 1, Z = 1;
ans[num[0]] = 1;
for (int i=1;i<n;i++) {
ans[num[i]] = max(ans[Left[num[i]]], ans[Right[num[i]]]) + 1;
Y = (Y + ans[num[i]]) % Mod;
Z = Z * ans[num[i]] % Mod;
}
printf("%d %d\n", int(Y), int(Z));
}
return 0;
}

我抄来的代码