P12704 Retribution

P12704 Retribution

我也不知道为什么能过做法。

考虑暴力缩点,然后做线段树合并。

细节上,由于要在可持久化线段树上合并,所以每次要新开节点,在合并的时候多剪枝减少栈调用和新开节点。
如果尝试将询问离线挂在每个 SCC 上的话, 的无序 vector 应该还不如存新节点。

如果乱开东西大概率空间会炸。
一共有 个 SCC,注意到线段树上实际有非常多节点都连向同一个节点每次新建的点并不会很多。
实际精细计算一下空间只要 个 int 就行。千万不要乱开 vector。

留下不到一百兆给栈调用和临时的队列啥的应该是差不多够了,事实上对于数据是刚刚好。

代码

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
const int N = 1510,LG = 21;
int n,m,q,seed;
char s[N][N];
int dx[4] = {-1,+1,+0,+0};
int dy[4] = {+0,+0,-1,+1};
inline int id(int x,int y){
return m*(x-1)+y;
}
struct{
int to,nex;
}edge[N*N*3];
int head[N*N],edge_num,ind[N*N];
inline void add(int x,int y){
edge[++edge_num].to = y;
edge[edge_num].nex = head[x];
head[x] = edge_num;
}
int dfn[N*N],low[N*N],st[N*N],id_scc[N*N],dtop,stop,col_cnt;
inline void tarjan(int now){
dfn[now] = low[now] = ++dtop;
st[++stop] = now;
for(int i=head[now];i;i=edge[i].nex){
int tto = edge[i].to;
if(!dfn[tto]){
tarjan(tto);
low[now] = Min(low[tto],low[now]);
}
else if(!id_scc[tto]) low[now] = Min(low[now],dfn[tto]);
}
if(low[now]==dfn[now]){
++col_cnt;
while(st[stop]^now){
id_scc[st[stop]] = col_cnt;
--stop;
}
id_scc[st[stop]] = col_cnt;
--stop;
}
head[now] = 0;
}
struct node{
int ls,rs;
}tr[N*N*LG];
int rt[N*N],cnt;
inline int merge(int u,int v,int nl,int nr){
if(!u || !v) return u|v;
if(u==v) return u;
int p = ++cnt;
if(nl==nr){
tr[p].ls = tr[u].ls | tr[v].ls;
return p;
}
int mid = (nl+nr) >> 1;
tr[p].ls = merge(tr[u].ls,tr[v].ls,nl,mid);
tr[p].rs = merge(tr[u].rs,tr[v].rs,mid+1,nr);
return p;
}
inline void insert(int &p,int nl,int nr,int x){
if(!p) p = ++cnt;
int mid = (nl+nr) >> 1;
if(nl==nr){
tr[p].ls = 1;
return ;
}
if(x<=mid) insert(tr[p].ls,nl,mid,x);
else insert(tr[p].rs,mid+1,nr,x);
}
inline int query(int p,int nl,int nr,int x){
if(!p) return 0;
int mid = (nl+nr) >> 1;
if(nl==nr) return tr[p].ls;
if(x<=mid) return query(tr[p].ls,nl,mid,x);
else return query(tr[p].rs,mid+1,nr,x);
}
inline void topo(){
queue <int> q;
for(int i=1;i<=col_cnt;++i){
insert(rt[i],1,col_cnt,i);
if(!ind[i]) q.push(i);
}
while(!q.empty()){
int now = q.front();
q.pop();
for(int i=head[now];i;i=edge[i].nex){
int tto = edge[i].to;
rt[tto] = merge(rt[tto],rt[now],1,col_cnt);
--ind[tto];
if(!ind[tto]) q.push(tto);
}
}
}
int main(){
// freopen("in.in","r",stdin);
// freopen("out.out","w",stdout);

read(n,m,q,seed);
init(seed);
for(int i=1;i<=n;++i){
scanf("%s",s[i]+1);
for(int j=1;j<=m;++j){
for(int t=0;t<4;++t){
if(s[i][j]=='U' && t==0) continue;
if(s[i][j]=='D' && t==1) continue;
if(s[i][j]=='L' && t==2) continue;
if(s[i][j]=='R' && t==3) continue;
int tx = i+dx[t];
int ty = j+dy[t];
if(tx<1 || tx>n || ty<1 || ty>m) continue;
add(id(i,j),id(tx,ty));
}
}
}
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
if(!dfn[id(i,j)]) tarjan(id(i,j));
}
}
edge_num = 0;
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
for(int t=0;t<4;++t){
if(s[i][j]=='U' && t==0) continue;
if(s[i][j]=='D' && t==1) continue;
if(s[i][j]=='L' && t==2) continue;
if(s[i][j]=='R' && t==3) continue;
int tx = i+dx[t];
int ty = j+dy[t];
if(tx<1 || tx>n || ty<1 || ty>m) continue;
if(id_scc[id(i,j)]^id_scc[id(tx,ty)]){
add(id_scc[id(tx,ty)],id_scc[id(i,j)]);
++ind[id_scc[id(i,j)]];
}
}
}
}
topo();
for(int i=1,a,b,x,y;i<=q;++i){
a = get(1,n); b = get(1,m); x = get(1,n); y = get(1,m);
query(rt[id_scc[id(a,b)]],1,col_cnt,id_scc[id(x,y)]) ? putchar('1') : putchar('0');
}

// fclose(stdin);
// fclose(stdout);
return 0;
}

后话

机房里大家都觉得这种东西非常假(我也觉得很假,但是就是写了然后过了),但实测这玩意跑得飞快。

我们在做合并的时候,只要发现处于同一个点就可以直接退出。而题里给出的图,会使得有非常多的线段树共用了同一个节点,合并复杂度事实上非常跑不满。

根据我本人精心构造的数据以及对数据点的判断,线段树新开的节点实际非常少大概在 ,合并次数大概在 级别。这是绰绰有余的。