orangejuice's blog

挂了请务必叫我

「BalticOI 2020」病毒

设点集大小为 \(N\) ,边集总长度 \(\sum k=M\) ,模板串总长 \(L=\sum ℓ\)

涉及到多串匹配的转移问题,容易想到 \(\text{AC}\) 自动机

因为本题状态非常少,可以暴力矩阵维护转移,暴力计算由状态 \(i\) 转移至状态 \(j\) ,且中途不匹配的最小长度

\(NL^2\) 个状态

给定的是一张有向图,可以用奇怪的 \(\text{Bellman-Ford,Dijkstra}\) 完成暴力转移

复杂度未知。。。上界应该比较高,但是鉴于常数小可以通过

\[ \ \]

\[\ \]

优化的转移:把每一条边的前缀拆出来,建立虚点

这样以来,所有状态转移可以归纳为 虚点+实点 \(\to\) 虚点/实点

一共有 \((N+M)L^2\) 个状态, \((N+M)L^2\) 种转移,每种转移涉及两个元素,产生 \(L\) 个元素

故对于每种转移的每一方,被遍历时都要枚举依次转移,共有 \(2(N+M)L^3\) 次转移

因此可以认为建立的图有 \((N+M)L^2\) 个点, \(2(N+M)L^3\) 条边

对此运行 类似 最短路算法即可

因此用 \(\text{Dijkstra}\) 维护转移的复杂度为 \(O(\ (N+M)L^3\ \log ((N+M)L^2)\ )\)

\[ \ \]

---以下是未优化Bellman-Ford代码----

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
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
#define pb push_back
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

char IO;
template <class T=int> T rd(){
T s=0; int f=0;
while(!isdigit(IO=getchar())) f|=IO=='-';
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int N=110,M=54;


int n,m,k;
typedef vector <int> V;
int trie[M][2],fail[M],mk[M],cnt;
void Ins(const V &S){
int now=0;
for(int i:S) {
int &nxt=trie[now][i];
if(!nxt) nxt=++cnt;
now=nxt;
}
mk[now]=1;
}
void Build(){
static queue <int> que;
rep(i,0,1) if(trie[0][i]) que.push(trie[0][i]);
while(!que.empty()) {
int u=que.front(); que.pop();
mk[u]|=mk[fail[u]];
rep(i,0,1){
int &v=trie[u][i];
if(v) que.push(v);
(!v?v:fail[v])=trie[fail[u]][i];
}
}
// delete illegal state
rep(i,0,cnt) rep(j,0,1) if(mk[trie[i][j]]) trie[i][j]=cnt+1;
}

V Read(){
V Res;
rep(i,1,rd()) Res.pb(rd());
return Res;
}

vector <V> G[N];
int E[N][N];
const ll INF=-1;
ll dis[N][M][M],ans[N];
int fl=1;
void Work(int u){
static ll F[M][M],G[M][M];
ll f=INF;
for(V S:(::G[u])) {
memset(F,255,sizeof F);
rep(i,0,cnt) F[i][i]=0;
for(int c:S) {
rep(i,0,cnt) rep(j,0,cnt) G[i][j]=F[i][j],F[i][j]=INF;
rep(i,0,cnt) rep(j,0,cnt) if(G[i][j]<INF)
rep(k,0,cnt) if(G[i][j]+dis[c][j][k]>=max(G[i][j],dis[c][j][k]))
cmin(F[i][k],G[i][j]+dis[c][j][k]);
}
rep(i,0,cnt) rep(j,0,cnt) if(dis[u][i][j]>F[i][j]) dis[u][i][j]=F[i][j],cmin(f,F[i][j]);
}
if(f!=INF) fl=1;
}

int main(){
n=rd()-1,m=rd(),k=rd();
rep(i,1,m) {
int u=rd(); V w=Read();
G[u].pb(w);
for(int v:w) E[v][u]=1;
}
rep(i,1,k) Ins(Read());
Build();
memset(dis,255,sizeof dis),memset(ans,255,sizeof ans);
rep(u,0,1) rep(i,0,cnt) if(!mk[i]) dis[u][i][trie[i][u]]=1;
while(fl){
fl=0;
rep(i,2,n) Work(i);
}
rep(i,2,n) rep(j,0,cnt) if(!mk[j]) cmin(ans[i],dis[i][0][j]);
rep(i,2,n) {
if(ans[i]==INF) puts("YES");
else printf("NO %llu\n",ans[i]);
}
}

----以下是无比垃圾的优化代码----

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
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
#define pb push_back
typedef pair <int,int> Pii;
#define mp make_pair
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
char IO;
int rd(){
int s=0;
while(!isdigit(IO=getchar()));
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return s;
}

const int N=210,M=52;
const ll INF=-1;

int n,m,k,c;
typedef vector <int> V;
int trie[M][2],fail[M],mk[M],cnt;
void Ins(const V &S){
int now=0;
for(int i:S) {
int &nxt=trie[now][i];
if(!nxt) nxt=++cnt;
now=nxt;
}
mk[now]=1;
}
void Build(){
static queue <int> que;
rep(i,0,1) if(trie[0][i]) que.push(trie[0][i]);
while(!que.empty()) {
int u=que.front(); que.pop();
mk[u]|=mk[fail[u]];
rep(i,0,1){
int &v=trie[u][i];
if(v) que.push(v);
(!v?v:fail[v])=trie[fail[u]][i];
}
}
mk[cnt+1]=1;
rep(i,0,cnt) rep(j,0,1) if(mk[i] || mk[trie[i][j]]) trie[i][j]=cnt+1;
}

V Read(){
V Res;
rep(i,1,rd()) Res.pb(rd());
return Res;
}

vector <Pii> G[N];
ll dis[N][M][M];
struct Node{
int u,s,t;
ll d;
bool operator < (const Node &__) const {
return d>__.d;
}
};
priority_queue <Node> que;
void Upd(int u,int s,int t,ll d){
if(mk[s]||mk[t]||dis[u][s][t]<=d) return;
dis[u][s][t]=d,que.push((Node){u,s,t,d});
}

int main(){
c=n=rd()-1,m=rd(),k=rd();
++c; // 建立一个空虚点
rep(i,1,m) {
int u=rd(); V w=Read();
int lst=n+1;
rep(j,0,w.size()-1) {
if(j==jend) G[lst].pb(mp(w[j],u)),G[w[j]].pb(mp(lst,u));
else G[lst].pb(mp(w[j],++c)),G[w[j]].pb(mp(lst,c)),lst=c;
}
}
rep(i,1,k) Ins(Read());
Build();
memset(dis,255,sizeof dis);
rep(i,0,cnt) if(!mk[i]) dis[n+1][i][i]=0; // 单位矩阵
rep(u,0,1) rep(i,0,cnt) Upd(u,i,trie[i][u],1);
while(!que.empty()) {
int u=que.top().u,s=que.top().s,t=que.top().t;
ll d=que.top().d; que.pop();
if(d>dis[u][s][t]) continue;
for(auto i:G[u]) {
int v=i.first,to=i.second;
if(u<=n) {
rep(i,0,cnt) if(dis[v][i][s]<INF) Upd(to,i,t,dis[v][i][s]+d);
} else {
rep(i,0,cnt) if(dis[v][t][i]<INF) Upd(to,s,i,d+dis[v][t][i]);
}
}
}
rep(i,2,n) {
ll ans=-1;
rep(j,0,cnt) if(!mk[j]) ans=min(ans,dis[i][0][j]);
if(ans==INF) puts("YES");
else printf("NO %llu\n",ans);
}
}

「BalticOI 2020」村庄 (贪心)

Subtask1: Min

考虑链上的情况,最优解肯定是两两相邻的交换,如果还有多,就再多交换一次

因此树上的也是类似,实际上就是求解一个最小边覆盖问题,选择一条边就是交换边两端的点编号

可以 \(O(n)\) 贪心/dp求解树上最小边覆盖

\[ \ \]

Subtask2: Max

考虑理想的最优情况:对于任意一条边,我们要求它被经过次数尽可能多

如果这条边两端子树大小分别为 \(a,b\) ,则它被经过的最多次数显然是 \(2\min\{a,b\}\)

考虑找到树的重心,以它为根,此时任意一颗真子树的大小 \(\leq \frac{n}{2}\)

为了构造最优答案,只需要每棵子树的集合相互错开即可

一种简单的构造方法是:取 \(\text{dfs}\) 序,平移 \(\frac{n}{2}\) 即可得到解

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pb push_back
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

char IO;
int rd(){
int s=0;
while(!isdigit(IO=getchar()));
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return s;
}

const int N=1e5+10,INF=1e9+10;

int n;
vector <int> G[N];
ll Min,Max;
int ans1[N],ans2[N];
int dep[N],fa[N],vis[N];
// 最小边覆盖贪心法
void pre_dfs(int u,int f){
ans1[u]=u,fa[u]=f;
for(int v:G[u]) if(v!=f) {
pre_dfs(v,u);
if(!vis[v]) vis[v]=vis[u]=1,swap(ans1[u],ans1[v]),Min+=2;
}
if(!vis[u] && !f) vis[u]=1,swap(ans1[u],ans1[G[u][0]]),Min+=2;
}

int A[N],C;
int mi=1e9,rt,sz[N];
// 找重心
void dfs(int u,int f){
sz[u]=1;
int ma=0;
for(int v:G[u]) if(v!=f) {
dfs(v,u);
cmax(ma,sz[v]),sz[u]+=sz[v];
Max+=2*min(n-sz[v],sz[v]);
}
cmax(ma,n-sz[u]);
if(mi>ma) mi=ma,rt=u;
}
// 遍历dfs序
void dfs_get(int u,int f) {
A[++C]=u;
for(int v:G[u]) if(v!=f) dfs_get(v,u);
}
int main(){
n=rd();
rep(i,2,n) {
int u=rd(),v=rd();
G[u].pb(v),G[v].pb(u);
}
pre_dfs(1,0),dfs(1,0),dfs_get(rt,0);
rep(i,1,n) ans2[A[i]]=A[(i+n/2-1)%n+1];
printf("%lld %lld\n",Min,Max);
rep(i,1,n) printf("%d ",ans1[i]);
puts("");
rep(i,1,n) printf("%d ",ans2[i]);
puts("");
}

「CCO 2020」千山万壑

性质推演

推论1:不选择非树边时,答案为 \(2(n-1)-\) 直径长

比较明显就不证了

推论2:最多只会选择一条非树边

考虑如果选择两条非树边,此时必然有答案 \(\ge n-1+3\lceil\frac{n}{3}\rceil\)

因为能够选择这样的非树边,则必然存在一条长度 \(>\frac{n}{3}\) 的路径,也就是说

直径长度 \(>\frac{n}{3}\) ,故此时选择直径更优

因此不会选择两条非树边

\[ \ \]

答案计算

下称起点终点路径 \((s,t)\) ,选择的边为 \((u,v,w)\)

如果 \((s,t)\)\((u,v)\) 无交,则无序额外计算贡献,此时贡献为

\(2(n-1)-dis(s,t)-(dis(u,v)-w)\)

\((s,t)\)\((u,v)\) 有交时,设交长度为 \(len\) ,则需要额外花费 \(2(len-1)\) 的代价取遍历交部分的点

\[ \ \]

求解

显然我们需要先知道 \(dis(u,v)\) ,可以 \(O(n)\) 预处理,我们选择的非树边一定满足 \(dis(u,v)>w\)

考虑抽直径构成序列 \(L_i\) ,然后考虑每一条非树边 \((u,v,w)\) 的贡献,设 \(u,v\) 在直径上对应根的编号为 \(x,y\)

如果 \(x=y\) ,显然可以选择直径,下面讨论 \(x<y\) 的情况

  1. \((s,t)\)\((u,v)\) 无交

1-1.对于 \((u,v)\) 之间的部分可以区间查询最值

1-2.两边预处理前缀/后缀最值

1-3.直径连接到 \(u\)\(v\) 子树的部分的答案可以换根 \(dp\) 预处理

  1. \((s,t)\)\((u,v)\) 有交,设交部分在直径上的区间为 \([l,r]\)

2-1. 相交跨过直径上多个点

2-1-1.若 \(x<l<r<y\) ,此时容易用线段树维护答案

2-1-2. 若 \(x<y , \text{and } (l=x \text{ or } r=y)\) ,此时显然两边部分最优一定是选择在直径上的部分

以下情况一定不优

另一边的答案可以在线段树上查询出来,是一个区间的前缀/后缀

2-2.若 \(l=r\) ,此时 \(l=x\)\(l=y\) ,且在 \([u,x],[v,y]\) 部分有交

容易发现,此时确定 \((s,t)\) 一边一定在直径的一端,另一端 \(x,y\) 对应的子树中

同样可以通过换根 \(dp\) 解决

区间查询,如果使用线段树解决,复杂度为 \(O(n+m\log n)\)

如果用奇怪数据结构(猫树),复杂度为 \(O(n\log n+m)\)

「CCO 2020」千山万壑

性质推演

推论1:不选择非树边时,答案为 \(2(n-1)-\) 直径长

比较明显就不证了

推论2:最多只会选择一条非树边

考虑如果选择两条非树边,此时必然有答案 \(\ge n-1+3\lceil\frac{n}{3}\rceil\)

因为能够选择这样的非树边,则必然存在一条长度 \(>\frac{n}{3}\) 的路径,也就是说

直径长度 \(>\frac{n}{3}\) ,故此时选择直径更优

因此不会选择两条非树边

\[ \ \]

答案计算

下称起点终点路径 \((s,t)\) ,选择的边为 \((u,v,w)\)

如果 \((s,t)\)\((u,v)\) 无交,则无序额外计算贡献,此时贡献为

\(2(n-1)-dis(s,t)-(dis(u,v)-w)\)

\((s,t)\)\((u,v)\) 有交时,设交长度为 \(len\) ,则需要额外花费 \(2(len-1)\) 的代价取遍历交部分的点

Snipaste_2021-03-03_09-44-08.png

\[ \ \]

求解

显然我们需要先知道 \(dis(u,v)\) ,可以 \(O(n)\) 预处理,我们选择的非树边一定满足 \(dis(u,v)>w\)

考虑抽直径构成序列 \(L_i\) ,然后考虑每一条非树边 \((u,v,w)\) 的贡献,设 \(u,v\) 在直径上对应根的编号为 \(x,y\)

确定非树边之后,我们需要选择一个最优的 \((s,t)\) ,答案计算上面已经提及

如果 \(x=y\) ,显然可以选择直径,下面讨论 \(x<y\) 的情况

  1. \((s,t)\)\((u,v)\) 无交

1-1.对于 \((u,v)\) 之间的部分可以区间查询子树最值

1-2.两边预处理前缀/后缀最值

1-3.直径连接到 \(u\)\(v\) 子树的部分的答案

就是一棵树剔除根到一个点路径之后的直径长度,可以换根 \(dp\) 预处理

  1. \((s,t)\)\((u,v)\) 有交,设交部分在直径上的区间为 \([l,r]\)

容易参分转化为求解: \(dis(s,t)+dis(u,v)-w-2(r-l)+2\) 最大值

Snipaste_2021-03-03_09-58-39.png

2-1. 相交跨过直径上多个点

2-1-1.若 \(x<l<r<y\) ,此时容易用线段树维护答案

合并时减去跨过长度的贡献即可

Snipaste_2021-03-03_10-00-38.png

2-1-2. 若 \(x<y , \text{and } (l=x \text{ or } r=y)\) ,此时显然两边部分最优一定是选择在直径上的部分

Snipaste_2021-03-03_10-01-17.png

以下情况一定不优

Snipaste_2021-03-03_10-03-49.png

另一边的答案可以在线段树上查询出来,是一个区间的前缀/后缀

2-2.若 \(l=r\) ,此时 \(l=x\)\(l=y\) ,且在 \([u,x],[v,y]\) 部分有交

Snipaste_2021-03-03_10-05-43.png

容易发现,此时确定 \((s,t)\) 一边一定在直径的一端,另一端 \(x,y\) 对应的子树中

同样可以通过换根 \(dp\) 解决

区间查询,如果使用线段树解决,复杂度为 \(O(n+m\log n)\)

如果用奇怪数据结构(猫树),复杂度为 \(O(n\log n+m)\)

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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef pair <int,int> Pii;
#define reg register
#define mp make_pair
#define pb push_back
#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

char IO;
template <class T=int> T rd(){
T s=0; int f=0;
while(!isdigit(IO=getchar())) f|=IO=='-';
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int N=5e5+10,M=2e6+10,INF=1e9+10;


int n,m;
int U[M],V[M],W[M],D[M],vis[N];

struct Edge{
int to,nxt;
} e[N<<1];
int head[N],ecnt;
void AddEdge(int u,int v){
e[++ecnt]=(Edge){v,head[u]};
head[u]=ecnt;
}

int ma,num,fa[N],dep[N];
vector <Pii> G[N];
int Find(int x){ return fa[x]==x?x:fa[x]=Find(fa[x]); }
void dfs1(int u,int f){
if(dep[u]>ma) ma=dep[u],num=u;
vis[fa[u]=u]=1;
for(Pii v:G[u]) if(vis[v.first]) D[v.second]=dep[u]+dep[v.first]-2*dep[Find(v.first)];
for(int i=head[u];i;i=e[i].nxt) {
int v=e[i].to;
if(v==f) continue;
dep[v]=dep[u]+1,dfs1(v,u);
}
fa[u]=f;
}
void dfs2(int u,int f){
if(dep[u]>ma) ma=dep[u],num=u;
fa[u]=f;
for(int i=head[u];i;i=e[i].nxt) {
int v=e[i].to;
if(v==f) continue;
dep[v]=dep[u]+1,dfs2(v,u);
}
}

int id[N],L[N],C,dp[N][2],dp2[N],g[N],h[N];
int subid[N];

void dfs3(int u,int f){
fa[u]=f;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(id[v]||v==f) continue;
dfs3(v,u);
cmax(dp2[u],dp2[v]);
int t=dp[v][0]+1;
if(t>dp[u][0]) dp[u][1]=dp[u][0],dp[u][0]=t;
else cmax(dp[u][1],t);
}
cmax(dp2[u],dp[u][0]+dp[u][1]);
}
void dfs4(int u,int f,int d=0){
dep[u]=d,subid[u]=d<=1?u:subid[f];
int tg[2]={-INF,-INF},th[2]={0,0};
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(id[v]||v==f) continue;
int x=dp[v][0]+1-(d?d:1e9);
if(x>tg[0]) tg[1]=tg[0],tg[0]=x;
else cmax(tg[1],x);

x=max(dp2[v],dp[v][0]+1);
if(x>th[0]) th[1]=th[0],th[0]=x;
else cmax(th[1],x);
}
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(id[v]||v==f) continue;
h[v]=max(h[u],th[0]==max(dp2[v],dp[v][0]+1)?th[1]:th[0]);
g[v]=max(g[u],tg[0]==dp[v][0]+1-(d?d:1e9)?tg[1]:tg[0]);
id[v]=id[u],dfs4(v,u,d+1);
}
if(f) cmax(g[u],dp[u][0]-d);
cmax(h[u],dp2[u]);
}

struct Node{
int len,ans,l,r;
Node operator + (const Node __) const {
Node res; res.len=len+__.len;
res.ans=max(ans,__.ans);
res.l=max(l,__.l-len),res.r=max(r-__.len,__.r);
cmax(res.ans,r+__.l+1);
return res;
}
} s[N<<2];
void Build(int p,int l,int r) {
if(l==r) {
s[p]=(Node){1,max(dp2[L[l]],dp[L[l]][0]),dp[L[l]][0],dp[L[l]][0]};
return;
}
int mid=(l+r)>>1;
Build(p<<1,l,mid),Build(p<<1|1,mid+1,r);
s[p]=s[p<<1]+s[p<<1|1];
}
Node Que(int p,int l,int r,int ql,int qr){
if(ql<=l && r<=qr) return s[p];
int mid=(l+r)>>1;
if(qr<=mid) return Que(p<<1,l,mid,ql,qr);
if(ql>mid) return Que(p<<1|1,mid+1,r,ql,qr);
return Que(p<<1,l,mid,ql,qr)+Que(p<<1|1,mid+1,r,ql,qr);
}
int ls[N],rs[N];

int main(){
n=rd();
rep(t,1,rd()){
int u=rd()+1,v=rd()+1,w=rd();
if(w==1) AddEdge(u,v),AddEdge(v,u);
else U[++m]=u,V[m]=v,W[m]=w,G[u].pb(mp(v,m)),G[v].pb(mp(u,m));
}
ma=-1,dfs1(1,0);
ma=-1,dep[num]=0,dfs2(num,0);
for(int u=num;u;u=fa[u]) L[id[u]=++C]=u;
rep(i,1,C) dfs3(L[i],0),g[L[i]]=-INF,dfs4(L[i],0);
rep(i,1,C) ls[i]=max(ls[i-1],i-1+dp[L[i]][0]);
drep(i,C,1) rs[i]=max(rs[i+1],C-i+dp[L[i]][0]);
Build(1,1,C);

int ans=C-1;
rep(i,1,m) if(D[i]>W[i]) {
int u=U[i],v=V[i],d=0;
if(id[u]>id[v]) swap(u,v);
if(id[u]==id[v]) d=C-1;
else {
if(fa[u]) {
cmax(d,h[u]);
int f=subid[u],t=fa[f];
cmax(d,id[u]-1+(dp[f][0]+1==dp[t][0]?dp[t][1]:dp[t][0]));
cmax(d,id[u]-1+g[u]+2);
} else cmax(d,dp[u][0]+id[u]-1);
if(fa[v]) {
cmax(d,h[v]);
int f=subid[v],t=fa[f];
cmax(d,C-id[v]+(dp[f][0]+1==dp[t][0]?dp[t][1]:dp[t][0]));
cmax(d,C-id[v]+g[v]+2);
} else cmax(d,dp[v][0]+C-id[v]);
cmax(d,ls[id[u]-1]),cmax(d,rs[id[v]+1]);

int g1=id[u]-1,g2=C-id[v];
cmax(d,g1+g2-(id[v]-id[u])+2);
if(id[u]+1<id[v]) {
Node t=Que(1,1,C,id[u]+1,id[v]-1);
cmax(d,t.ans);
cmax(d,t.l+g1+1);
cmax(d,t.r+g2+1);
}
}
cmax(ans,D[i]-W[i]+d);
}
printf("%d\n",2*(n-1)-ans);
}

「CCO 2020」购物计划

核心思想:堆+调整临近

\(x_i=y_i=1\)

这个限制相当于每一组内的权值排名可以确定,设组内为 \(A_{i,j}(j\ge 1)\)

那么我们一个方案的选择可以用 \(M\) 个指针 \(P_i\) 表示,和为 \(\displaystyle \sum A_{i,P_i}\)

考虑用调整的方式解决这个问题,大体思路上,我们可以记录当前移动指针 \(P_p\)

每次可以选择移动 \(P_p\) ,或者某一个 \(P_{i},i>p\)

如果直接进行,每次移动的指针数量是 \(O(m)\) 级别的,显然不可行

考虑优化一下进行,每次只能选取 \(i=p+1\)

此时,编号小的会先被移动

为了保证答案单调性,我们需要将 \(A_{i,2}-A_{i,1}\) 较小的组先移动

同时,并不是每一个组都会被移动,因此转移还要支持一个特殊回撤操作,来撤回当前组的指针

也就是说,若 \(P_p=2\) ,可以选择把 \(P_p\) 回撤为 \(1\) ,然后将 \(P_{p+1}\) 改为 \(2\)

由此,每个点状态可以选择:

1.移动自己

2.移动下一个

3.若 \(P_p=2\) ,回撤自己,同时移动下一个

这样的调整法,可以保证每一个状态恰好有一个前驱,且转移过程中值不断变大

由此可以 \(O(k)\) 状态数进行调整,用堆维护,复杂度为 \(O(k\log k)\)

\[ \ \]

组内调整

一个组内会选择若干个数 \(A_{b_i},i\in [1,c]\)

初始最小值,显然满足 \(b_i=i\)

类似的,我们记录当前指针 \(p\) ,前驱指针 \(l\) ,后继指针 \(r\)

显然 \(p\) 要往后移,且不能达到 \(r\) ,因此决策只有两种

1.移动前驱 \(l\) ,并将当前指针变为前驱

2.移动自己 \(p\)

这样的调整是固定个数的,因此,一开始就把 \(c\in[x_i,y_i]\) 的所有情况插入即可

\[ \ \]

最后,将两部分一同进行,每次组间调整时,通过组内调整查询答案

总的组内和组间调整次数均为 \(O(k)\) ,状态数分别不超过 \(2k,3k\)

复杂度为 \(O(k\log k)\)

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pb push_back
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
char IO;
int rd(){
int s=0;
while(!isdigit(IO=getchar()));
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return s;
}
enum{N=200010,LINF=1ll<<60};

int n,m,k,I[N]; ll L;
struct Group{
vector <int> A;
int l,r,c;
struct Node{
int l,p,r; ll s;
bool operator < (const Node &__) const { return s>__.s; }
};
priority_queue <Node> que;
vector <ll> V;
void Init(){
l=rd(),r=rd(),sort(A.begin(),A.end()),c=A.size();
if(c<l) {
rep(i,1,k) puts("-1");
exit(0);
}
r=min(r,c);
rep(i,0,l-1) L+=A[i];
ll s=0;
if(!l) V.pb(0);
rep(i,0,c-1) {
if(i>=r) break;
s+=A[i];
if(i>=l-1) que.push((Node){i-1,i,c-1,s});
}
}
void Next(){
if(que.empty()) return V.pb(LINF);
Node t=que.top(); que.pop();
V.pb(t.s);
if(t.p<t.r) que.push((Node){t.l,t.p+1,t.r,t.s-A[t.p]+A[t.p+1]}); // Move current point
if(~t.l && t.l<t.p-1) que.push((Node){t.l-1,t.l+1,t.p-1,t.s-A[t.l]+A[t.l+1]}); // Move previous point
}
// get kth sum
ll operator [] (const int &k){
while((int)V.size()<k) Next();
return V[k-1];
}
} S[N];

struct Node{
int x,y; ll s;
bool operator < (const Node &__) const { return s>__.s; }
};
priority_queue <Node> que;

int main(){
n=rd(),m=rd(),k=rd();
rep(i,1,n) { int x=rd(); S[x].A.pb(rd()); }
rep(i,1,m) S[i].Init();
printf("%lld\n",L),k--;
rep(i,1,m) I[i]=i;
sort(I+1,I+m+1,[&](int x,int y){ return S[x][2]-S[x][1]<S[y][2]-S[y][1]; });
que.push((Node){1,2,L-S[I[1]][1]+S[I[1]][2]});
while(k) {
Node t=que.top(); que.pop();
if(t.s>=LINF) break;
k--,printf("%lld\n",t.s);
int i=I[t.x],j=I[t.x+1];
que.push((Node){t.x,t.y+1,t.s-S[i][t.y]+S[i][t.y+1]});// Move current point
if(j) que.push((Node){t.x+1,2,t.s-S[j][1]+S[j][2]}); // Move next point
if(t.y==2 && j) que.push((Node){t.x+1,2,t.s-S[i][2]+S[i][1]-S[j][1]+S[j][2]});
// Back current point ,and move next point
}
while(k--) puts("-1");
}

「CEOI2019」建造摩天楼

显然是倒着考虑删除每个大楼,此时每次面临的情况都是一个子问题

下文称当前局面未被删除的大楼为黑点,其余为白点

子问题有解的充要条件是:黑点之间能 8-连通

当前一个点能够被删掉的条件是:

1.这个点能够连通到无穷处

2.这个点不是当前8-连通图的割点

\[ \ \]

考虑用一个简单的方法维护条件1:

将一开始每个黑点周围的白点取出,按照白点之间4-连通构建连通块

能够4-连通接触到最外层连通块的黑点满足条件1

每次删除黑点之后,增加能够连通的白点,每个白点只会增加一次

ps:寻找最外层4-连通白点的一个方法:找到x最大的白点

\[ \ \]

接下来考虑如何判定一个点 \(u\) 是否是割点:

首先删除 \(u\) 之后,周围8连通内能够构成多个连通块,可以发现大致可以归结为以下几种情况,其中x,y为黑点

多个 \(x\) 表示 \(x\) 在其中任何一个位置都可以

1.
x y
x u y
x y
2.
x y
u y
y y y

对于这两种情况,只要白点1和白点2在同一4-连通块,割掉 \(u\) 就会分开x和 \(y\)

由此,每次插入一个白点,可以 \(O(1)\) 检测一个点是否合法

简单讨论可以发现,会被影响合法性的点,一定在新加入最外层连通块的白点周围

这样总共check了 \(O(n)\) 次,每次用一个堆/set维护能选的最大编号的点即可

ps:代码非常丑非常垃圾。。。

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
#include<bits/stdc++.h>
using namespace std;

typedef pair <int,int> Pii;
#define mp make_pair
#define pb push_back
#define x first
#define y second
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)

char IO;
template <class T=int> T rd(){
T s=0; int f=0;
while(!isdigit(IO=getchar())) f|=IO=='-';
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int N=1.3e6+10,INF=1e9+10;
const int z[5][4]={{1,0},{0,-1},{-1,0},{0,1}};
const int Z[9][4]={{1,1},{1,0},{1,-1},{0,-1},{-1,-1},{-1,0},{-1,1},{0,1}};

int n,m,k;
int F[N];
int Find(int x){ return F[x]==x?x:F[x]=Find(F[x]);}
void Union(int x,int y){ F[Find(x)]=Find(y); }
map <Pii,int> B,W;
set <int> st;
vector <int> ans;
Pii A[N],P[N];
int ma=-1e9,maxid;
int G[N][4];
void Ins(int x,int y){
// Insert white point
Pii T=mp(x,y);
if(B.count(T)) return ;
if(!W.count(T)) P[W[T]=++m]=T,F[m]=m;
int u=W[T];
if(x>ma) ma=x,maxid=u;
rep(i,0,3) {
int x1=x+z[i][0],y1=y+z[i][1];
if(W.count(mp(x1,y1))) {
int v=W[mp(x1,y1)];
G[u][i]=v;
G[v][(i+2)%4]=u;
Union(u,v);
}
}
}

int del[N],vis[N],reach[N];
int Check(int u){
if(del[u]||!reach[u]) return 0;
static int I[8];
int c=0;
rep(i,0,7) {
Pii T=mp(A[u].x+Z[i][0],A[u].y+Z[i][1]);
if(W.count(T)) I[i]=Find(W[T]);
else I[i]=0,c++;
}
for(int i=1,t=0;t<4;t++,i=(i+2)%8){
int j=(i+2)%8;
if(I[i] && I[i]==I[j] && !I[(i+1)%8] && c>1) return 0;
}
if((!I[0]||!I[1]||!I[2]) && (!I[4]||!I[5]||!I[6]) && I[3] && I[3]==I[7]) return 0;
if((!I[2]||!I[3]||!I[4]) && (!I[6]||!I[7]||!I[0]) && I[1] && I[5]==I[1]) return 0;
return 1;
}
void ReCheck(int u){
auto it=st.find(u);
if(it!=st.end()) st.erase(it);
if(Check(u)) st.insert(u);
}

vector <int> tmp;
void dfs(int u){
if(!u) return;
if(vis[u]) return;
vis[u]=1;
rep(i,0,3) {
if(G[u][i]) dfs(G[u][i]);
Pii T=mp(P[u].x+z[i][0],P[u].y+z[i][1]);
if(B.count(T)) reach[B[T]]=1;
}
rep(dx,-1,1) rep(dy,-1,1) if(dx||dy) {
Pii T=mp(P[u].x+dx,P[u].y+dy);
if(B.count(T)) tmp.pb(B[T]);
}
}
void Dfs(int u){
dfs(u);
sort(tmp.begin(),tmp.end()),tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end());
for(int i:tmp) ReCheck(i);
tmp.clear();
}

void Del(int u){
// delete black point
del[u]=1;
B.erase(A[u]),Ins(A[u].x,A[u].y);
Dfs(W[A[u]]);
}

int main(){
n=rd(),rd();
rep(i,1,n) A[i].x=rd(),A[i].y=rd(),B[A[i]]=i;
rep(i,1,n) F[i]=i;

// Check NO
rep(i,1,n) {
rep(dx,-1,1) rep(dy,-1,1) {
Pii T=mp(A[i].x+dx,A[i].y+dy);
if(B.count(T)) Union(i,B[T]);
}
}
rep(i,1,n) if(Find(i)!=Find(1)) return puts("NO"),0;

rep(i,1,n) rep(dx,-1,1) rep(dy,-1,1) if(dx||dy) Ins(A[i].x+dx,A[i].y+dy);
Dfs(maxid);

rep(i,1,n) {
int u=*st.rbegin();
auto it=st.end(); --it;st.erase(it);
Del(u),ans.pb(u);
}
reverse(ans.begin(),ans.end());
puts("YES");
for(int i:ans) printf("%d\n",i);
}

「CEOI2020」星际迷航

首先是最简单的判断是否必胜的 \(dp\) 转移 \(\displaystyle dp_u=\bigcup_{v\in son_u} \text{not } dp_{v}\)

考虑第 \(i+1\) 层对于第 \(i\) 层的贡献,实际上只和 \(i+1\) 层有多少个点 \(dp\) 值为0/1有关

下面称 \(dp\) 值为0/1的点为 败/胜 点

考虑对于确定第 \(i\) 层的根为某一节点 \(root\) 之后

在某一个点下面接一个胜点,或者在一个胜点下面接一个点,对于胜败情况没有影响

在一个败点下面接一个败点,可能会导致一段连续祖先段的胜败翻转

考虑对于每个 \(u\) 作为根求出:

1.没有接上下一层时的胜败情况 : \(dp_u\)

2.有多少个节点接上一个败点之后,会导致根的胜败情况翻转: \(R_u\)

这是一个换根 \(dp\) 问题

具体来说, \(R_u\) 的值,根据子节点中败点的个数可以得到转移:

1.没有败点,那么就是所有子节点 \(R\) 之和+自己

2.恰好有一个败点,那么就是败点的 \(R\)

对此,每个点维护子节点中败点的个数,然后换根 \(dp\) 即可

求出每个 \(root\) 的1,2后,可以用一个矩阵维护每层的转移,就不再赘述

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pb push_back
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)

char IO;
int rd(){
int s=0;
while(!isdigit(IO=getchar()));
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return s;
}

const int N=1e5+10,INF=1e9+10,P=1e9+7;

int n;
ll m;
struct Mat{
int a[2][2];
void clear(){ memset(a,0,sizeof a); }
Mat operator * (const Mat x) const {
Mat res; res.clear();
rep(i,0,1) rep(j,0,1) rep(k,0,1) res.a[i][k]=(res.a[i][k]+1ll*a[i][j]*x.a[j][k])%P;
return res;
}
} Res,X;

int F[N],FC[N],FR[N],son[N][2],SR[N];
// F子树胜败,FC子节点败点个数,FR子树翻转点个数,son存储两个儿子中的败点,SR存储儿子中的FR之和
int G[N],GC[N],GR[N];
// F,GC,GR是子树外的
int dp[N],R[N],fa[N];
// dp,R即上文所述

vector <int> E[N];
void dfs1(int u,int f) {
// 子树处理
fa[u]=f;
for(int v:E[u]) if(v!=f) {
dfs1(v,u);
FC[u]+=!F[v];
if(!F[v]) {
if(!son[u][0]) son[u][0]=v;
else son[u][1]=v;
}
SR[u]+=FR[v];
}
F[u]=FC[u]>0;
FR[u]=!F[u];
if(FC[u]<=1) for(int v:E[u]) if(v!=f && F[u]!=F[v]) FR[u]+=FR[v];
}

//换根dp
void dfs2(int u,int f) {
if(f) GC[u]=G[u]=!G[f],GR[u]=GR[f]+!G[u];
else GC[u]=G[u]=0,GR[u]=1;
dp[u]=F[u]|G[u];
if(!dp[u]) R[u]=FR[u]+GR[u]-1;
else if(FC[u]+G[u]==1) {
if(G[u]) R[u]=GR[u];
else R[u]=FR[u];
}
for(int v:E[u]) if(v!=f) {
GC[u]=FC[u]-!F[v]+(f?!G[f]:0);
G[u]=GC[u]>0;
GR[u]=0;
if(!G[u]) GR[u]=GR[f]+SR[u]-FR[v]+1;
else if(GC[u]==1) {
if(son[u][0] && son[u][0]!=v) GR[u]=FR[son[u][0]];
else if(son[u][1] && son[u][1]!=v) GR[u]=FR[son[u][1]];
else GR[u]=GR[f];
}
dfs2(v,u);
}
}

int cnt[2];
int main(){
n=rd(),scanf("%lld",&m);
rep(i,2,n) {
int u=rd(),v=rd();
E[u].pb(v),E[v].pb(u);
}
dfs1(1,0),dfs2(1,0);
// 矩阵处理
rep(i,1,n) {
cnt[dp[i]]++;
X.a[1][dp[i]]=(X.a[1][dp[i]]+n)%P;
X.a[0][!dp[i]]=(X.a[0][!dp[i]]+R[i])%P;
X.a[0][dp[i]]=(X.a[0][dp[i]]+n-R[i])%P;
}
m--,Res.a[0][0]=Res.a[1][1]=1;
while(m) {
if(m&1) Res=Res*X;
X=X*X;
m>>=1;
}
int x=(1ll*Res.a[0][0]*cnt[0]+1ll*Res.a[1][0]*cnt[1])%P;
int y=(1ll*Res.a[0][1]*cnt[0]+1ll*Res.a[1][1]*cnt[1])%P;
// 得到第一层败/胜 的个数,与第0层合并
int ans=0;
if(!dp[1]) ans=1ll*x*R[1]%P;
else ans=(1ll*x*(n-R[1])+1ll*n*y)%P;
printf("%d\n",ans);
}

「ROI 2016 Day1」人烟之山

题目大意:

\(n\) 段折线, \(m\) 个查询点 \(A\) (在折线以上),设折线拐点为 \(X_i\)

求折线上在查询点投影两边最近的位置 \(B\) ,且直线 \(AB\) 与折线有非边缘的交点 (即从 \(A\) 点看过来会被折线遮住)

题目分析:

\(B,C\) 点满足的条件就是其旁边的直线 \(L\)\(x_A\) 处的值 \(>y_A\)

Snipaste_2021-02-20_13-43-59.png

即找到最近的红色直线

Solution1 \(O(n\log ^2n)\)

以求左边为例

考虑二分答案,求出拐点为 \(X_i,i\ge mid\) 的直线中是否存在红色直线

也就是求最大值是否 \(>y_A\) ,维护直线最大值,并且区间查询,可以暴力可持久化李超树来解决

因为是求左边的,所以每条直线更新的范围 \(>X_i\) ,李超树区间更新复杂度为 \(O(\log ^2n)\)

李超树查询复杂度为 \(O(\log n)\) ,加上二分,查询为 \(O(\log ^2n)\)

\[ \ \]

Solution2 \(O(n\log n)\)

依然考虑二分,但是这次考虑在线段树上二分

对于所有的 \(X_i\) 建立线段树,区间 \([L,R]\) 内维护一个 \(X_i,i\in [L,R]\) 的上凸包,静态维护最大值

凸包可以归并子节点来建立,预处理复杂度为 \(O(n\log n)\)

如果查询直接在凸包上二分,复杂度会增加一个 \(\log n\)

解决方法是:将所有查询的 \(x_A\) 排序,然后在凸包上查询时就可以做到线性

因此复杂度为 \(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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define pb push_back
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
char buf[200000],*p1,*p2;
#define getchar() (((p1==p2)&&(p2=(p1=buf)+fread(buf,1,200000,stdin))),*p1++)
char IO;
int rd(){
int s=0; int f=0;
while(!isdigit(IO=getchar())) f|=IO=='-';
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int N=4e5+10,U=1e9+10;

int n,m,K[N],X[N],L[N],R[N];
ll Y[N];
struct Que{
int x,y,id;
bool operator < (const Que __) const {
return x<__.x;
}
} Q[N];
struct Node{
int k; ll b;
ll operator [] (const ll x) const {
return 1ll*k*x+b;
}
friend db Cross(Node x,Node y){ return 1.0*(y.b-x.b)/(x.k-y.k); }
bool operator < (const Node __) const {
return k<__.k||(k==__.k && b<__.b);
}
};
vector <Node> H[N<<2];
vector <Node> ::iterator P[N<<2];
void Build(int p,int l,int r){
if(l==r) return H[p].pb((Node){K[l],Y[l]-1ll*K[l]*X[l]}),P[p]=H[p].begin(),void();
int mid=(l+r)>>1;
Build(p<<1,l,mid),Build(p<<1|1,mid+1,r);
int p1=0,s1=H[p<<1].size(),p2=0,s2=H[p<<1|1].size(),R=-1;
auto Ins=[&](Node L) {
while(~R && H[p][R].b<=L.b) R--,H[p].pop_back();
while(R>0 && Cross(H[p][R],H[p][R-1])>=Cross(H[p][R],L)-1e-8) R--,H[p].pop_back();
H[p].pb(L),R++;
};
while(p1<s1 || p2<s2) {
if(p1<s1 && (p2==s2 || H[p<<1][p1]<H[p<<1|1][p2])) Ins(H[p<<1][p1++]);
else Ins(H[p<<1|1][p2++]);
}
P[p]=H[p].begin();
}

ll Que(int p,int x){
while(P[p]+1!=H[p].end() && (*(P[p]+1))[x]>=(*P[p])[x]) P[p]++;
return (*P[p])[x];
}
int QueL(int p,int l,int r,int x,int qx,int y){
if(x<l || Que(p,qx)<=y) return 0;
if(l==r) return l;
int mid=(l+r)>>1,t;
if(x>mid && (t=QueL(p<<1|1,mid+1,r,x,qx,y))) return t;
return QueL(p<<1,l,mid,x,qx,y);
}
int QueR(int p,int l,int r,int x,int qx,int y){
if(x>r || Que(p,qx)<=y) return n+1;
if(l==r) return l;
int mid=(l+r)>>1,t;
if(x<=mid && (t=QueR(p<<1,l,mid,x,qx,y))<=n) return t;
return QueR(p<<1|1,mid+1,r,x,qx,y);
}

int main(){
n=rd(),m=rd();
rep(i,1,n) {
X[i]=X[i-1]+rd(),K[i]=rd();
Y[i]=Y[i-1]+1ll*(X[i]-X[i-1])*K[i];
}
rep(i,1,m) Q[i].x=rd(),Q[i].y=rd(),Q[i].id=i;
sort(Q+1,Q+m+1);
Build(1,1,n);
int p=1;
rep(i,1,m) {
while(p<=n && X[p]<Q[i].x) p++;
L[Q[i].id]=QueL(1,1,n,p-1,Q[i].x,Q[i].y);
R[Q[i].id]=QueR(1,1,n,p+(X[p]==Q[i].x),Q[i].x,Q[i].y);
}
rep(i,1,m) printf("%d %d\n",X[L[i]],X[R[i]-1]);
}

「CEOI2018」斐波那契表示法

思路:维护当前数值的唯一表示法,然后根据唯一表示法来确定答案

Part1 唯一表示法

任何一个数 \(x\) 有唯一表示 \(P_i\) ,满足 \(x=\sum F_{P_i},P_i<P_{i+1}-1\)

即不会出现相邻两项

依次插入每一个数 \(x\) ,考虑可能出现的情况

  1. \(x\) 一位以及前后为空,那么直接插入

  2. \(x\) 一位为空,且 \(x-1\) 为空, \(x+1\) 已经出现

删除 \(x+1\) ,插入 \(x+2\)

  1. \(x\) 一位为空,且 \(x+1\) 为空, \(x-1\) 已经出现

删除 \(x-1\) ,插入 \(x+1\)

  1. \(x\) 一位有

先删除 \(x\) ,然后插入 \(x+1,x-2\)

对于操作1,2,3以及4中的 \(x+1\) ,每次操作增加 \(O(1)\) 个元素,每次递归进行删除 \(O(1)\) 个元素

操作次数为均摊 \(O(n)\)

对于 \(4\) 操作中的 \(x-2\) ,如果 \(x-2\) 已经出现就会不断进行递归

最终的效果就是所有被操作到的 \(x-2,x-4,x-6\ldots\) 向右平移了一位

大致如此,实际情况比较复杂,要讨论x-2=0,x-2<0等等情况

用一棵平衡树维护 \(P_i-P_{i-1}\) 的值即可,4操作可以二分左边第一个>2的元素,然后进行平移

最终复杂度为 \(O(n\log n)\)

\[ \ \]

Part2 dp求答案

令边界 \(P_0=0\) ,根据上面维护的 \(\delta_i=P_i-P_{i-1}\)

考虑根据 \(\delta_i\) 求解答案

显然一个数 \(x\) 可以下分为 \(x\) 或者 \(x-1,x-2\)\(x-1,x-3,x-4\)\(x-1,x-3,x-5,x-6\ldots\)

且不能碰到前面的数

简单分析发现 \(P_i\)\(\lceil \frac{\delta_i}{2}\rceil\) 种下分方案

然而, \(P_{i-1}\) 如果被下分,那么 \(P_{i-1}\) 这一位会消失,变成 \(P_{i-1}-1\) 作为限制点

也就是说, \(P_{i-1}\) 的下分会影响到 \(\delta_i\) ,使得 \(\delta_i\rightarrow \delta_i+1\)

那么依次考虑每个 \(\delta_i\) ,令 \(dp_{i,f}\) 表示前 \(i\) 个,最后一个是否下分的方案数,可以 \(dp\) 求解

由于要动态维护,因此可以考虑用一个类似矩阵的东西来维护区间的dp情况

在平衡树中 \(up\) 维护答案即可

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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair <int,int> Pii;
#define reg register
#define mp make_pair
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

char IO;
template <class T=int> T rd(){
T s=0; int f=0;
while(!isdigit(IO=getchar())) f|=IO=='-';
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int N=4e5+10,P=1e9+7;

int n;
struct Node{
int l,r,ma,len;
int a[2][2];
void clear(){ memset(a,0,sizeof a); }
Node(){ }
Node(int x){
l=r=ma=len=x;
rep(i,0,1) {
a[i][0]=1;
a[i][1]=(x-1+i)/2;
}
}
Node operator + (const Node _) const {
Node res; res.l=l,res.r=_.r,res.ma=max(ma,_.ma),res.len=len+_.len;
res.a[0][0]=(1ll*a[0][0]*_.a[0][0]+1ll*a[0][1]*_.a[1][0])%P;
res.a[1][0]=(1ll*a[1][0]*_.a[0][0]+1ll*a[1][1]*_.a[1][0])%P;
res.a[0][1]=(1ll*a[0][0]*_.a[0][1]+1ll*a[0][1]*_.a[1][1])%P;
res.a[1][1]=(1ll*a[1][0]*_.a[0][1]+1ll*a[1][1]*_.a[1][1])%P;
return res;
}
} s[N],val[N];

int rt,ls[N],rs[N],key[N];
void Up(int x){
s[x]=val[x];
if(ls[x]) s[x]=s[ls[x]]+s[x];
if(rs[x]) s[x]=s[x]+s[rs[x]];
}
int U(int x,int y){
if(!x||!y) return x|y;
if(key[x]<key[y]) return rs[x]=U(rs[x],y),Up(x),x;
return ls[y]=U(x,ls[y]),Up(y),y;
}

Pii Lower(int x,int len){
if(len<=0 || !x) return mp(0,x);
if(s[x].len<=len) return mp(x,0);
if(s[ls[x]].len>=len) {
Pii y=Lower(ls[x],len);
return ls[x]=y.second,Up(x),mp(y.first,x);
} else {
Pii y=Lower(rs[x],len-s[ls[x]].len-val[x].len);
return rs[x]=y.first,Up(x),mp(x,y.second);
}
}

void EraseEnd(int &x){
if(!rs[x]){ x=ls[x]; return; }
static int T[N],C;
for(int y=x;y;y=rs[y]) T[++C]=y;
rs[T[C-1]]=ls[T[C]];
drep(i,C-1,1) Up(T[i]);
C=0;
}
void AddR(int x,int y){
if(!x) return;
if(rs[x]) return AddR(rs[x],y),Up(x);
val[x]=Node(val[x].len+y),Up(x);
}
void AddL(int x,int y){
if(!x) return;
if(ls[x]) return AddL(ls[x],y),Up(x);
val[x]=Node(val[x].len+y),Up(x);
}

Pii Split(int x){
if(s[x].ma<=2) return mp(0,x);
if(val[x].ma<=2 && s[rs[x]].ma<=2) {
Pii y=Split(ls[x]);
return ls[x]=y.second,Up(x),mp(y.first,x);
} else {
Pii y=Split(rs[x]);
return rs[x]=y.first,Up(x),mp(x,y.second);
}
}
Pii Split2(int x){
if(s[x].ma<=2) return mp(x,0);
if(s[ls[x]].ma>2 || val[x].ma>2) {
Pii y=Split2(ls[x]);
return ls[x]=y.second,Up(x),mp(y.first,x);
} else {
Pii y=Split2(rs[x]);
return rs[x]=y.first,Up(x),mp(x,y.second);
}
}
int New(int x){ return key[++n]=rand(),s[n]=val[n]=Node(x),n; }

void Ins(int x){
if(x<0) return;
cmax(x,1);
if(!rt) { rt=New(x); return; }
if(s[rt].len<x-1) {
rt=U(rt,New(x-s[rt].len));
return;
}
if(s[rt].len==x-1) {
EraseEnd(rt);
return Ins(x+1);
}
Pii t=Lower(rt,x);
if(s[t.first].len!=x) {
if(x>1) {
Pii y=Lower(t.first,x-1);
if(s[y.first].len==x-1) {
AddR(y.first,s[y.second].len),rt=U(y.first,t.second);
return Ins(x+1);
}
t.first=U(y.first,y.second);
}
if(s[t.first].len==x+1) {
Pii y=Split2(t.second);
AddL(y.second,-1);
int d=s[t.first].r+s[y.first].len+1;
EraseEnd(t.first);
rt=U(U(t.first,New(d)),y.second);
return;
}
int d=s[t.first].len-x;
AddR(t.first,-d);
rt=U(U(t.first,New(d)),t.second);
return;
}
if(s[t.second].l==2) return AddL(t.second,s[t.first].r),EraseEnd(t.first),rt=U(t.first,t.second),Ins(x+1),Ins(x-2);
Pii y=Split(t.first); AddL(t.second,-1);
if(!y.first) {
if(s[y.second].l==1) {
AddL(y.second,1),rt=U(y.second,t.second);
return;
}
rt=U(U(New(1),y.second),t.second);
return;
}
if(s[y.first].len>3 && s[y.first].r==3) {
EraseEnd(y.first),AddR(y.first,2);
rt=U(U(U(y.first,New(2)),y.second),t.second);
return;
}
AddR(y.first,-2);
rt=U(U(U(y.first,New(3)),y.second),t.second);
}


int main(){
rep(kase,1,rd()) Ins(rd()),printf("%d\n",(s[rt].a[0][0]+s[rt].a[0][1])%P);
}

「ROI 2016 Day2」二指禅

考虑对于每个点,有前缀和后缀两种转移

对于两种转移分别建立 \(\text{trie}\) 树,并且维护最小权值,对于 \(dp_i\) ,可以匹配一段后缀从 \(j<i\) 得到

也可以匹配一段前缀更新 \(j>i\) ,分别 \(O(n)\) 枚举在两棵树上匹配即可完成转移

暴力转移复杂度为 \(O(n^2)\)

考虑优化转移效率,以 \(dp_i\) 匹配某一段前缀向 \(dp_j(j>i)\) 转移为例

Part1 考虑求出最大的 \(j\)

这个问题实际上就是求出每一个后缀的最大前缀匹配,也就是一个 \(\text{exkmp}\) 问题

而且这题是一个多模板串版本,并不能用 \(\text{exkmp}\) 解决

Solution 1

是把所有前缀hash值取出来,放到 \(\text{hash_table}\) 里,然后二分长度+ \(\text{hash_table}\) 查询,复杂度为 \(O(\log L)\)

Solution 2

树剖+hash

对于 \(\text{trie}\) 树树剖并且预处理hash值,重链上二分hash匹配,轻儿子暴力走,复杂度为 \(O(\log ^2L)\)

如果使用全局平衡二叉树,可以做到单次查询复杂度为 \(O(\log L)\) ,常数应该远小于 \(\text{hash_table}\)

\[ \ \]

\[ \ \]

Part2 在 \(j_{max}\) 基础上转移

考虑先求出最大的 \(j\) 之后,得到其对应的 \(\text{trie}\) 树节点 \(u\) ,在的祖先 \(u\) 中,每一种不同的权值更新一次

显然每一个祖先长度不同,因此最多只有 \(O(\sqrt L)\) 中不同的权值

因此可以for过去更新每一种权值,每次更新是一段区间,可以用树状数组维护

但是实际上极限情况下段极短,可以暴力枚举区间,所以并不是真的 \(O(\sqrt L\log L)\)

比较容易说明复杂度的优化方法是:

采用分块 \(O(1)\) 更新, \(O(\sqrt L)\) 查询

这样可以做到稳定 \(O(\sqrt L)\) 完成转移

\[\ \]

因此预处理复杂度为 \(O(m\log L-m\log ^2L)\) ,转移复杂度为 \(O(m\sqrt L-m\sqrt L\log L)\)

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }

const int N=3e5+10,INF=1e9+10,D=6;

int n,m;
char T[N];
int Pow1[N],Pow2[N];
int S[N];
const ll K1=19260817,P1=114514;
const ll K2=1e9+13,P2=1919810;

struct Solver{
int S[N];
int trie[N][2],s[N],cnt,H1[N],H2[N];
int fa[N],top[N],sz[N],h1[N],h2[N],son[N],lst[N],bot[N],L[N],id[N],dfn,dep[N];
// lst[i] 记录祖先中第一个s[f]!=s[i]的节点
void dfs(int u){
sz[u]=1;
if(s[u]==s[fa[u]]) lst[u]=lst[fa[u]];
else lst[u]=fa[u];
rep(i,0,1) {
int v=trie[u][i];
if(!v) continue;
fa[v]=u,dep[v]=dep[u]+1;
h1[v]=(h1[u]*K1+i+1)%P1;
h2[v]=(h2[u]*K2+i+1)%P2;
dfs(v);
sz[u]+=sz[v];
if(son[u]==0 || sz[v]>sz[son[u]]) son[u]=v;
}
}
void dfs(int u,int t){
id[L[u]=++dfn]=u;
bot[top[u]=t]=u; if(son[u]) dfs(son[u],t);
for(int v:trie[u]) if(v&&v!=son[u]) dfs(v,v);
}
void Ins(char *T,int l,int w){
int now=0;
rep(i,1,l) {
int c=T[i]-'0';
if(!trie[now][c]) s[trie[now][c]=++cnt]=2e9;
cmin(s[now=trie[now][c]],w);
}
}
void Init(){
rep(i,1,n) {
H1[i]=(H1[i-1]*K1+S[i]+1)%P1;
H2[i]=(H2[i-1]*K2+S[i]+1)%P2;
}
dfs(0),dfs(0,0);
}
int Que(int i) {
// 求i 的最长匹配位置
int u=0;
while(i<=n) {
if(!trie[u][S[i]]) break;
if(trie[u][S[i]]!=son[u]){ u=trie[u][S[i++]]; continue; }
int l=1,r=min(n-i+1,dep[bot[top[u]]]-dep[u]),res=1;
while(l<=r){
int mid=(l+r)>>1;
if( (H1[i+mid-1]-1ll*H1[i-1]*Pow1[mid]%P1+P1)%P1 == (h1[id[L[u]+mid]]-1ll*h1[u]*Pow1[mid]%P1+P1)%P1 &&
(H2[i+mid-1]-1ll*H2[i-1]*Pow2[mid]%P2+P2)%P2 == (h2[id[L[u]+mid]]-1ll*h2[u]*Pow2[mid]%P2+P2)%P2)
l=mid+1,res=mid;
else r=mid-1;
}
i+=res,u=id[L[u]+res];
}
return u;
}
} X,Y;

struct BIT{
ll s[N];
void Init(){
memset(s,127,sizeof s);
}
void Add(int p,ll x){
while(p) cmin(s[p],x),p-=p&-p;
}
ll Que(int p){
ll res=1e18;
while(p<=n) cmin(res,s[p]),p+=p&-p;
return res;
}
} TX,TY;
ll dp[N];

int main(){
rep(i,Pow1[0]=Pow2[0]=1,N-1) {
Pow1[i]=Pow1[i-1]*K1%P1;
Pow2[i]=Pow2[i-1]*K2%P2;
}
scanf("%d%d%*d",&n,&m);
rep(i,1,n) scanf("%1d",S+i),X.S[i]=Y.S[n-i+1]=S[i];
rep(t,1,m) {
int w,l; scanf("%d%s",&w,T+1),l=strlen(T+1);
X.Ins(T,l,w),reverse(T+1,T+l+1),Y.Ins(T,l,w);
}
X.Init(),Y.Init();

rep(i,1,n) dp[i]=1e18;
TX.Init(),TY.Init();
TY.Add(1,0);
rep(i,0,n) {
// 暴力维护转移
if(i) {
cmin(dp[i],TX.Que(i));
int u=Y.Que(n-i+1);
while(u) {
int l=Y.dep[Y.lst[u]]+1,r=Y.dep[u];
if(r-l+1<=D) rep(j,l,r) cmin(dp[i],dp[i-j]+Y.s[u]);
else cmin(dp[i],TY.Que(i-r+1)+Y.s[u]);
u=Y.lst[u];
}
}
TY.Add(i+1,dp[i]);
if(i==n||dp[i]==1e18) continue;
int u=X.Que(i+1);
while(u) {
int l=X.dep[X.lst[u]]+1,r=X.dep[u];
if(r-l+1<=D) rep(j,l,r) cmin(dp[i+j],dp[i]+X.s[u]);
else TX.Add(i+r,dp[i]+X.s[u]);
u=X.lst[u];
}
}
printf("%lld\n",dp[n]<1e18?dp[n]:-1);
}

「ROI 2017 Day 1」虎 (计算几何)

题意:(交互题)

已知 \(n\) 个点, \(m\) 次询问,每次询问交互器随机生成一个位置的关键点,要求在 \(k\) 次查询中给出一个合法解

查询:一个凸包,返回关键点是否在凸包中

解:一个凸包,包含关键点,且不包含其它点,保证有界

对关键点的包含是包括了边界线的,其它点的包含不包括边界线,凸包均要按照顺时针给出

Solution 1 (未完全实现)

先将给定点分层转化为若干凸包,容易通过二分得到关键点所在的层

如果关键点以内不再有凸包,显然得到一个解

否则,一个合法的解一定是在两层凸包中某一层选取两个点,另一层选取一个点得到的三角形

先考虑内层选两个点的情况,令二分边界为内层凸包上点的编号, \(l,r\)

考虑实现一个操作,对于 \(l,r\) ,找到其连接的直线在顺时针方向上在外层凸包上切到的段

由此得到一个凸包进行查询,即可进行二分,最终 \(l+1=r\) 时,再进行一次上述操作得到一组解(不一定合法)

如果不合法,同理再在外层上二分一次

最终写道第一种情况弃掉了。。。

\[ \ \]

\[ \ \]

Solution2 随机分裂

良好的随机分裂可以跑到max query times<=33的好成绩

考虑一个非常简单的剖开 \(n\) 个点的方法:

1.找到这些点的凸包,从点集中删掉

2.从剩余点中选择一个作为中心,分别与凸包上的点连边,将平面分开

3.确定每个三角形中包含的点,加上三个顶点,继续进行剖分

最终当凸包以外不再有点时,结束

\[ \ \]

查询也是比较显然的:

对于当前凸包及其中心,二分找到关键点对应的位置,然后继续进行,直到不存在中心

二分方法:

凸包构成一圈,取一个点为 \(l\) ,顺时针180以内的范围最大角度的点为 \(r\) ,每次将中心和 \(l,mid\) 这连续一段查询判断是否包含

这样存在的问题是:可能不在 \(l\) 的180范围内,需要在开始二分前判断一下

十分朴素的实现也可以获得90分的好成绩

优化:

每次选取中心时,多随机几次,估价找到一个最优剖分即可

Loj Submission -包含大量调试语句和内嵌的交互部分

Loj Submission

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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef pair <int,int> Pii;
#define reg register
#define mp make_pair
#define pb push_back
#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T& a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T& a,T b){ ((a<b)&&(a=b)); }

char IO;
int rd(){
int s=0,f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

bool Mbe;
const int N=5010;

int n,q;

// 计算几何部分
namespace Geometry{
struct Node{
ll x,y;
Node(){}
Node(ll x,ll y):x(x),y(y){}
Node operator - (const Node __) const { return Node(x-__.x,y-__.y); }
Node operator + (const Node __) const { return Node(x+__.x,y+__.y); }
ll operator ^ (const Node __) const { return 1ll*x*__.y-1ll*y*__.x; }
bool operator == (const Node __) const { return x==__.x && y==__.y; }
void Read(){ x=rd(),y=rd(); }
} A[N],QuePoint;
typedef vector <int> V;
// check close wise
int chk(int i,int j,int k,int bound=0){ return ((A[k]-A[i])^(A[j]-A[i]))>=bound; }
int chk(Node i,int j,int k,int bound=0){ return ((A[k]-i)^(A[j]-i))>=bound; }
// this is clockwise
int CheckConvex(const V &P){
int n=P.size();
rep(i,0,n-1) {
int j=(i+1)%n,k=(j+1)%n;
if(!chk(P[j],P[k],P[i])) return 0;
}
return 1;
}
// this is clock-wise
int CheckIn(const V&P,Node X,int bound=0){
rep(i,0,P.size()-1) {
int j=(i+1)%P.size();
if(chk(X,P[i],P[j],bound)) continue;
return 0;
}
return 1;
}
int Check(V P){
if(P.size()<=2) return 0;
printf("? %llu ",P.size());
for(int i:P) printf("%d ",i);
puts("");
fflush(stdout);
static char s[5]; scanf("%s",s);
return *s=='Y';
}
void Output(V P){
printf("! %llu ",P.size());
for(int i:P) printf("%d ",i);
puts("");
fflush(stdout);
}
V Convex(V P){
if(P.size()<=2) return P;
V Ans;
int n=P.size();
static int I[N],mk[N],S[N],T;
rep(i,0,n-1) mk[I[P[i]]=i]=0;
sort(P.begin(),P.end(),[&](int x,int y){ return A[x].x<A[y].x || (A[x].x==A[y].x && A[x].y>A[y].y); });
T=0;
rep(i,0,n-1) {
while(T>1 && ((A[P[i]]-A[S[T]])^(A[S[T-1]]-A[S[T]]))>0 ) T--;
S[++T]=P[i];
}
rep(i,1,T) Ans.pb(S[i]),mk[I[S[i]]]=1;
sort(P.begin(),P.end(),[&](int x,int y){ return A[x].x<A[y].x || (A[x].x==A[y].x && A[x].y<A[y].y); });
T=0;
rep(i,0,n-1) {
while(T>1 && ((A[P[i]]-A[S[T]])^(A[S[T-1]]-A[S[T]]))<0 ) T--;
S[++T]=P[i];
}
drep(i,T,1) if(!mk[I[S[i]]]) Ans.pb(S[i]);
return Ans;
}
}
using namespace Geometry;


const int M=2e5+10;

V C[M],S[M];
int K[M],D[M],E[M],rt,m,mk[M];
// 剖分
// C凸包,S子节点
// K中心,D凸包上0号点对应的180范围内的最大点,E凸包上D号点对应180范围内的最大点
// m个数
void Build(int &u,V P) {
sort(P.begin(),P.end());
int n=P.size();
C[u=++m]=Convex(P);
rep(i,0,n-1) mk[i]=0;
if(C[u].size()==P.size()) return;
for(int i:C[u]) mk[lower_bound(P.begin(),P.end(),i)-P.begin()]=1;
V T; rep(i,0,n-1) if(!mk[i]) T.pb(P[i]);
n=C[u].size();
// 获取剖分结果
auto Get=[&]() {
if(rand()&1) K[u]=T[(T.size()/(rand()%3+2)+rand()%8)%T.size()];
else K[u]=T[rand()%T.size()];
V P=T; P.erase(lower_bound(P.begin(),P.end(),K[u]));
D[u]=E[u]=0;
while(D[u]<n-1 && chk(K[u],C[u][0],C[u][D[u]+1])) D[u]++;
E[u]=D[u];
while(E[u]<n-1 && chk(K[u],C[u][D[u]],C[u][E[u]+1])) E[u]++;
vector <V> ST(n);
for(int x:P) {
rep(i,0,n-1) {
if(chk(K[u],C[u][i],x) && chk(K[u],x,C[u][(i+1)%n])) {
ST[i].pb(x);
break;
}
}
}
rep(i,0,n-1) ST[i].pb(K[u]),ST[i].pb(C[u][i]),ST[i].pb(C[u][(i+1)%n]);
return ST;
};
vector <V> st;
int mi=1e9;
rep(kase,1,5) {
int d=D[u],e=E[u],k=K[u];
auto ST=Get();
int now=0;
for(V j:ST) cmax(now,(int)j.size());
if(now>mi){ D[u]=d,E[u]=e,K[u]=k; continue; }
st=ST,mi=now;
}
rep(i,0,n-1) {
int x; Build(x,st[i]);
S[u].pb(x);
}
}

void Init() {
V T(n);
rep(i,0,n-1) T[i]=i+1;
Build(rt,T);
}

void Find(int u=rt){
if(!S[u].size()) return Output(C[u]);
int n=C[u].size();
int l,r;
auto Get=[&](int l,int r) {
V T; T.pb(K[u]);
l%=n,r%=n;
for(int i=l;;i=(i+1)%n) {
T.pb(C[u][i]);
if(i==r) break;
}
return Check(T);
};
if(Get(0,D[u])) l=0,r=D[u];
else if(Get(D[u],E[u])) l=D[u],r=E[u];
else l=E[u],r=n;
while(l+1<r) {
int mid=(l+r)>>1;
if(Get(l,mid)) r=mid;
else l=mid;
}
Find(S[u][l]);
}

int main(){
n=rd();
rep(i,1,n) A[i].Read();
Init();
q=rd();
rep(_,1,q) Find();
}

0%