Newnode's NOI 模拟赛 第三题(可持久化线段树优化建图+Tarjan)

第三题

问题描述

这里写图片描述

输入格式

第一行一个整数n。

接下来n行每行3个整数表示宇宙的三个属性(ai,bi,ci)。

输出格式

n行每行一个整数,如果第i个宇宙可以成为最大宇宙则第i行为1,否则为0。

样例输入 1

3
1 3 2
2 1 3
3 2 1

样例输出 1

1
1
1

样例输入 2

10
1 10 4
2 7 9
3 3 7
4 4 8
5 2 1
6 9 3
7 6 10
8 8 5
9 5 6
10 1 2

样例输出 2

1
1
1
1
0
1
1
1
1
0

提示

对于20%的数据n<=10;
对于40%的数据n<=500;
对于60%的数据n<=2000;
对于100%的数据n<=100000。


首先考虑暴力的做法。
如果宇宙A大于宇宙B,那么从A到B连一条边,这样构好图后,可能成为最大的宇宙的点肯定满足从它出发可以到达所有能到达它的点。因此就是缩点后入度为0的点,注意到题目中给的$a_i,b_i,c_i$都是一个$1-n$的排列,那么任意两点间一定有一条边,因此入度为0的点只能有一个,反证即可证明。

因此可以暴力枚举点对来连边,然后跑Tarjan即可。这样的时空复杂度都是$O(n^2)$的。

考虑优化,用到了可持久化线段树来优化建边。
首先,三种属性中至少有两种大于,可以通过枚举是那两种,变成恰有两种属性值大于,然后做三次就行了。

那么现在问题转化成$X_i<X_j,Y_i<Y_j$的点对连$j\rightarrow i$的边。
注意到这是个二维偏序的问题,考虑一维排序来处理,那么现在按照$X_i$递增来考虑,需要将$i$点向$Y$值在$1-Y_i$中的点连边,直接连最多可能连$n^2$条边,但是可以用可持久化线段树来处理。

处理的方法是对$Y$值建立线段树,线段树中每个点向儿子连边,叶子节点向对应的$Y_i$对应的(原图中的)点连边,这样的话,如果要向$[1,Y_i]$中每个点连边,就只需要向线段树上,管辖$[1,Y_i]$这个区间的那个点连边就行了。最多拆成$\log_n$个线段树区间,因此每个点最多连出去$\log n$条边。

但是注意到还需要保证只向插入时间在它前面的点连边,因此需要可持久化,维护插入的版本信息。每个节点的新版本向旧版本连边就能保证后加的点能够走到所有新加的点。
如果不可持久化就可能使得先加的点能通过线段树上的边指向后加的点。

这样操作之后,边数就控制在了$O(n\log n)$级别。
总时间复杂度$O(n\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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stack>
#define N 100005
#define M 20000000
using namespace std;
char buf[1<<20],*p1,*p2;
#define GC (p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?0:*p1++)
inline void _R(int &x)
{
char t=GC;
while(t<48||t>57)t=GC;
for(x=0;t>47&&t<58;t=GC)x=(x<<1)+(x<<3)+t-48;
}
int n,A[N],B[N],C[N],id[N],D[M],Ans;
int tot,rt,ls[M],rs[M];
int TOT,LA[M],EN[M],NE[M];
int Tot,La[M],En[M],Ne[M];
int dfn[M],low[M],be[M],scc,VT;
stack<int>S;
bool mark[M],use[M];
void ADD(int x,int y)
{
if(!x||!y)return;
TOT++;
EN[TOT]=y;
NE[TOT]=LA[x];
LA[x]=TOT;
}
void add(int x,int y)
{
Tot++;
En[Tot]=y;
Ne[Tot]=La[x];
La[x]=Tot;
}
int CP(int x)
{
int o=++tot;
ls[o]=ls[x];
rs[o]=rs[x];
return o;
}
void Ins(int &p,int l,int r,int k,int d)
{
int o=CP(p);ADD(o,p);p=o;
if(l==r){ADD(p,d);return;}
int mid=l+r>>1;
if(k<=mid)Ins(ls[p],l,mid,k,d),ADD(p,ls[p]);
else Ins(rs[p],mid+1,r,k,d),ADD(p,rs[p]);
}
void Add(int p,int l,int r,int k,int d)
{
if(!p)return;
if(r<k){ADD(d,p);return;}
int mid=l+r>>1;
Add(ls[p],l,mid,k,d);
if(k>mid)Add(rs[p],mid+1,r,k,d);
}
void Work(int x[],int y[])
{
for(int i=1;i<=n;i++)id[x[i]]=i;
for(int i=1;i<=n;i++)
{
Ins(rt,1,n,y[id[i]],id[i]);
Add(rt,1,n,y[id[i]],id[i]);
}
}
void Tarjan(int x)
{
int i,y;
mark[x]=1;S.push(x);
dfn[x]=low[x]=++VT;
for(i=LA[x];i;i=NE[i])
{
y=EN[i];
if(!dfn[y])Tarjan(y),low[x]=min(low[x],low[y]);
else if(mark[y])low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x])
{
scc++;
do{
y=S.top();
S.pop();
mark[y]=0;
be[y]=scc;
}while(x!=y);
}
}
void DFS(int x)
{
if(Ans)return;
if(use[x]){Ans=x;return;}
for(int i=La[x];i;i=Ne[i])
{
int y=En[i];D[y]--;
if(!D[y])DFS(y);
}
}
int main_main()
{
int i,j,k;
_R(n);tot=n;
for(i=1;i<=n;i++)_R(A[i]),_R(B[i]),_R(C[i]);
rt=0;Work(A,B);
rt=0;Work(B,C);
rt=0;Work(A,C);
for(i=1;i<=tot;i++)if(!dfn[i])Tarjan(i);
for(i=1;i<=tot;i++)
for(j=LA[i];j;j=NE[j])
{
k=EN[j];
if(be[i]!=be[k])add(be[i],be[k]),D[be[k]]++;
}
for(i=1;i<=n;i++)use[be[i]]=1;
for(i=1;i<=scc;i++)if(!D[i])DFS(i);
for(i=1;i<=n;i++)printf("%d\n",be[i]==Ans);
}
const int main_stack=16;
char my_stack[128<<20];
int main() {
__asm__("movl %%esp, (%%eax);\n"::"a"(my_stack):"memory");
__asm__("movl %%eax, %%esp;\n"::"a"(my_stack+sizeof(my_stack)-main_stack):"%esp");
main_main();
__asm__("movl (%%eax), %%esp;\n"::"a"(my_stack):"%esp");
return 0;
}