[省选联考 2021 A/B 卷]宝石

(〇)考场上的一些ideas

其实这部分已经是正解了。

第一档部分分:

开考5min:

一个很显然的暴力:对于每次询问都从 \(u\) 找到 \(v\)25ptsgets。

时间复杂度\(\Theta(nq)\)

第二档部分分(\(m \leq 300\)):

开考10min:

首先一个处理树上路径问题的套路:将 \((u,v)\) 拆成 \((u, lca)\)\((lca, v)\)

对于每一个节点,假设当前节点的颜色在排列中是第 \(i\) 个。

考虑用 \(f(i, j)=x\) 表示 \(i\) 沿着父边向上走遇到了连续 \(j\) 个排列的末端节点是 \(x\)

对于每个节点的 \(f\) 都可以 \(\Theta(m)\) 暴力求解(每个节点都一步步向上跳,记录当前遇到了连续多少个排列)。

注意: 由于路径 \((u, lca)\) 与路径 \((lca, v)\) 在树上的方向是相反的(一个向根走, 另一个向叶子走), 因此需要处理 \(f_1\)\(f_2\) 分别表示向上走或者向下走。

对于每个询问, 可以树剖查询,然后二分查找最长路径。

时间复杂度:\(\Theta(nm+q\log n\log m)\)

第三档部分分 (\(u_i=i,v_i=i+1\)):

开考30min:

这是链的情况, 只需要考虑序列上如何做。

由于第二档部分分 \(m\leq 300\) 给我们的启示, 我们可以用 \(f(i, j)=x\) 记录第 \(i\) 个位置的后连续 \(j\) 个排列末端所在的位置是 \(x\)

但是,由于\(m\leq 2\times 10^5\), 开不下那么大的空间。

受到第二档部分分的启发, 可以考虑分块[1]。(通俗的说,就是用时间换空间)

既然我无法把后面的连续 \(m\) 个排列的位置全部记下来, 那我就只记后面连续的 \(\sqrt m\) 个排列的位置。

每次查询, 都尽可能的往最远处跳, 最多只用跳 \(\sqrt m\) 次。

这样一来, 就的到了一个十分“优秀”的算法。

时间复杂度: \(\Theta (n\sqrt m+q\sqrt m)\)

第四档部分分(正解):

开考45min:

实际上,只需要将以上两档部分分结合起来就是正解了。

正解包含了两档部分分中最麻烦的部分。

众所周知, 树剖就是将树上的问题转化为序列上的问题。

既然可以序列上对颜色分块, 那么是否也可以在树上对颜色分块呢? 答案是肯定的。

具体实现只需要用树剖将询问转化为序列上一段段的区间, 然后再套用序列上问题的做法即可。(注意同样要维护树上向上或是向下的路径)

时间复杂度:\(\Theta(n\sqrt m+q\sqrt m\log n)\)

接下来是我牢骚时刻, 建议跳过

咦, t1 just this? 于是再写正解的路上狂奔。

开考1h:

正解怎么这么难写呀!(coding...)

开考1.5h:

怎么细节这么多呀!!(debugging...)

开考2h:

终于调对了, 测 \(gem2.in\), 嗯, 找不到差异。

\(gem3.in\), ****, 怎么爆栈了, 怎么开栈(无能狂怒)

问监考员, 监考员:我好久没用 windows 了。(我也不知道怎么开栈)

此时 Godのfather 不知道, 他将存颜色的数组开小了, 这是他噩梦的开始。

考场代码分数:

更改数组大小后分数(由于考场上不知所措, 时间复杂度也写假了):

(一)关于正解

也许调整一下块长分块就能过了呢?

事实上, 我们并不需要分块(来自一名被 \(m\leq 300\) 误导, 认为是分块的菜鸡)。

倍增可以达到分块同样的效果。(并且常数更小, 代码更短)

(此时已经通过树剖将树上的问题转化为了序列上的问题)具体做法是, \(f(i, j)=x\) 表示位置 \(i\) 后连续 \(2^j\) 个排列的位置是 \(x\)

至于查询, 类似于倍增法求 \(lca\) , 先跳大步, 再跳小步逼近。

时间复杂度降到了:\(\Theta(n\log m+q\log m\log n)\)

code(分块):

#include <bits/stdc++.h>
using namespace std;
inline int read(){
	int w = 0, f = 1; char ch = getchar();
	while(ch < '0' or ch > '9') {if(ch == '-') f = -f; ch = getchar();}
	while(ch >= '0' and ch <= '9') w = w*10 + ch - '0', ch = getchar();
	return w*f;
}
const int maxm = 5e4 + 5, maxn = 2e5 + 5;
int N, M, c, P[maxn], w[maxn], head[maxn], Next[maxn<<1], ver[maxn<<1], tot, p[maxm], blen;
void add(int x, int y){
	ver[++tot] = y, Next[tot] = head[x], head[x] = tot;
}
int d[maxn], f[maxn], son[maxn], siz[maxn], top[maxn], id[maxn], cnt;
pair<int, int> nw[maxn];
int nxt[maxn][305][2], num, len[maxn][2];
vector<int> col[maxm];
struct node{
	int l, r; bool res;
}ask[1005];
void dfs1(int x, int fa){
	f[x] = fa, siz[x] = 1, d[x] = d[fa] + 1;
	for(int i=head[x]; i; i=Next[i]){
		int y = ver[i];
		if(y == fa) continue;
		dfs1(y, x);
		siz[x] += siz[y];
		if(siz[y] > siz[son[x]]) son[x] = y;
	}
}
void dfs2(int x, int TOP){
	top[x] = TOP, id[x] = ++cnt;
	if(!son[x]) return ;
	dfs2(son[x], TOP);
	for(int i=head[x]; i; i=Next[i]){
		int y = ver[i];
		if(y == f[x] or y == son[x]) continue;
		dfs2(y, y);
	}
}
int lca(int x, int y){
	while(top[x] != top[y]){
		if(d[top[x]] < d[top[y]]) swap(x, y);
		x = f[top[x]];
	}
	if(d[x] > d[y]) swap(x, y);
	return x;
}
void get_up(int x, int y){
	while(top[x] != top[y]){
		ask[++num] = (node){id[top[x]], id[x], 1};
		x = f[top[x]];
	}
	if(id[y] != id[x]) ask[++num] = (node){id[y], id[x], 1};
}
void get_down(int x, int y){
	int s = num;
	while(top[x] != top[y]){
		ask[++num] = (node){id[top[x]], id[x], 0};
		x = f[top[x]];
	}
	ask[++num] = (node){id[y], id[x], 0};
	for(int i=1; i<=(num-s)/2; i++){
		swap(ask[s+i], ask[num-i+1]);
	}
}
int Find1(int x, int l){//这是我考场上时间复杂度写假的部分, 多了1个log
	if(x < l or x == 0) return 0;
	int L = 0, R = len[x][1];
	while(L <= R){
		int mid = (L+R)>>1;
		if(nxt[x][mid][1] >= l) L = mid + 1;
		else R = mid-1;
	}
	int sum = R;
	if(R != 0) sum += Find1(nxt[x][R][1], l);
	return sum;
}
int Find2(int x, int r){//同上
	if(x > r or x == 0) return 0;
	int L = 0, R = len[x][0];
	while(L <= R){
		int mid = (L+R)>>1;
		if(nxt[x][mid][0] <= r) L = mid + 1;
		else R = mid - 1;
	}
	int sum = R;
	if(R != 0) sum += Find2(nxt[x][R][0], r);
	return sum;
}
int main(){
	N = read(), M = read(), c = read();
	for(int i=1; i<=M; i++) P[i] = read(), p[P[i]] = i;
	for(int i=1; i<=N; i++) w[i] = read();
	for(int i=1; i<N; i++){
		int x = read(), y = read();
		add(x, y), add(y, x);
	}
	dfs1(1, 0);//树剖
	dfs2(1, 1);
	for(int i=1; i<=N; i++) nw[i] = make_pair(id[i], w[i]);
	sort(nw+1, nw+N+1); //树上问题转序列上问题(根据dfs序)
	blen = 300;//块长
	for(int i=1; i<=N; i++) col[nw[i].second].push_back(nw[i].first);
	for(int i=1; i<=N; i++){//分块:向下的路径
		nxt[i][0][0] = i;
		int s = p[nw[i].second];
		for(int j=1; j<=min(blen, M); j++){
			vector<int>::iterator it = lower_bound(col[P[s+j]].begin(), col[P[s+j]].end(), nxt[i][j-1][0]);
			if(it != col[P[s+j]].end()) nxt[i][j][0] = *it, len[i][0] = j;
			else break;
		}
	}
	for(int i=N; i>=1; i--){//分块: 向上的路径
		nxt[i][0][1] = i;
		int s = p[nw[i].second];
		for(int j=1; j<=min(blen, M); j++){
			vector<int>::iterator it = lower_bound(col[P[s+j]].begin(), col[P[s+j]].end(), nxt[i][j-1][1]);
			if(it != col[P[s+j]].begin()) nxt[i][j][1] = *(--it), len[i][1] = j;
			else break;
		}
	}
	int q = read();
	while(q--){
		int u = read(), v = read();
		int LCA = lca(u, v);
		num = 0;
		get_up(u, LCA);//记录路径上划分出来的区间
		get_down(v, LCA);
		int cnt = 1;
		for(int i=1; i<=num; i++){
			if(cnt == M+1) break;
			int l = ask[i].l, r = ask[i].r; bool res = ask[i].res;
			if(res){//判断该区间在树上是向上还是向下
				vector<int>::iterator it = upper_bound(col[P[cnt]].begin(), col[P[cnt]].end(), r);
				if(it == col[P[cnt]].begin()) continue;
				int s = *(--it);
				if(s < l) continue;
				cnt += Find1(s, l) + 1;
			}
			else{
				vector<int>::iterator it = lower_bound(col[P[cnt]].begin(), col[P[cnt]].end(), l);
				if(it == col[P[cnt]].end()) continue;
				int s = *it;
				if(s > r) continue;
				cnt += Find2(s, r) + 1;
			}
		}
		printf("%d\n", cnt - 1);
	}
	return 0;
} 

code(倍增)(只改动了一点):

#include <bits/stdc++.h>
using namespace std;
inline int read(){
	int w = 0, f = 1; char ch = getchar();
	while(ch < '0' or ch > '9') {if(ch == '-') f = -f; ch = getchar();}
	while(ch >= '0' and ch <= '9') w = w*10 + ch - '0', ch = getchar();
	return w*f;
}
const int maxm = 5e4 + 5, maxn = 2e5 + 5;
int N, M, c, P[maxn], w[maxn], head[maxn], Next[maxn<<1], ver[maxn<<1], tot, p[maxm], blen;
void add(int x, int y){
	ver[++tot] = y, Next[tot] = head[x], head[x] = tot;
}
int d[maxn], f[maxn], son[maxn], siz[maxn], top[maxn], id[maxn], cnt;
pair<int, int> nw[maxn];
int nxt[maxn][20][2], num, len[maxn][2];
vector<int> col[maxm];
struct node{
	int l, r; bool res;
}ask[100005];
void dfs1(int x, int fa){
	f[x] = fa, siz[x] = 1, d[x] = d[fa] + 1;
	for(int i=head[x]; i; i=Next[i]){
		int y = ver[i];
		if(y == fa) continue;
		dfs1(y, x);
		siz[x] += siz[y];
		if(siz[y] > siz[son[x]]) son[x] = y;
	}
}
void dfs2(int x, int TOP){
	top[x] = TOP, id[x] = ++cnt;
	if(!son[x]) return ;
	dfs2(son[x], TOP);
	for(int i=head[x]; i; i=Next[i]){
		int y = ver[i];
		if(y == f[x] or y == son[x]) continue;
		dfs2(y, y);
	}
}
int lca(int x, int y){
	while(top[x] != top[y]){
		if(d[top[x]] < d[top[y]]) swap(x, y);
		x = f[top[x]];
	}
	if(d[x] > d[y]) swap(x, y);
	return x;
}
void get_up(int x, int y){
	while(top[x] != top[y]){
		ask[++num] = (node){id[top[x]], id[x], 1};
		x = f[top[x]];
	}
	if(id[y] != id[x]) ask[++num] = (node){id[y], id[x], 1};
}
void get_down(int x, int y){
	int s = num;
	while(top[x] != top[y]){
		ask[++num] = (node){id[top[x]], id[x], 0};
		x = f[top[x]];
	}
	ask[++num] = (node){id[y], id[x], 0};
	for(int i=1; i<=(num-s)/2; i++){
		swap(ask[s+i], ask[num-i+1]);
	}
}
int Find1(int x, int l){// 改动: 倍增先跳大步, 再跳小步
	int sum = 0;
	for(int i=15; i>=0; i--){
		if(nxt[x][i][1] == 0) continue;
		if(nxt[x][i][1] >= l){
			x = nxt[x][i][1];
			sum += (1<<i);
		}
	}
	return sum;
}
int Find2(int x, int r){//同上
	int sum = 0;
	for(int i=15; i>=0; i--){
		if(nxt[x][i][0] == 0) continue;
		if(nxt[x][i][0] <= r){
			x = nxt[x][i][0];
			sum += (1<<i);
		}
	}
	return sum;
}
int main(){
	N = read(), M = read(), c = read();
	for(int i=1; i<=M; i++) P[i] = read(), p[P[i]] = i;
	for(int i=1; i<=N; i++) w[i] = read();
	for(int i=1; i<N; i++){
		int x = read(), y = read();
		add(x, y), add(y, x);
	}
	dfs1(1, 0);
	dfs2(1, 1);
	for(int i=1; i<=N; i++) nw[i] = make_pair(id[i], w[i]);
	sort(nw+1, nw+N+1);
	for(int i=1; i<=N; i++) col[nw[i].second].push_back(nw[i].first);
	for(int i=N; i>=1; i--){//改动: 换成了倍增
		int s = p[nw[i].second];
		vector<int>::iterator it = upper_bound(col[P[s+1]].begin(), col[P[s+1]].end(), i);
		if(it != col[P[s+1]].end()){
			nxt[i][0][0] = *it;
			for(int j=1; j<=15; j++) nxt[i][j][0] = nxt[nxt[i][j-1][0]][j-1][0];
		}
	}
	for(int i=1; i<=N; i++){
		int s = p[nw[i].second];
		vector<int>::iterator it = upper_bound(col[P[s+1]].begin(), col[P[s+1]].end(), i);
		if(it != col[P[s+1]].begin()){
			nxt[i][0][1] = *(--it);
			for(int j=1; j<=15; j++) nxt[i][j][1] = nxt[nxt[i][j-1][1]][j-1][1];
		}
	}
	int q = read();
	while(q--){
		int u = read(), v = read();
		int LCA = lca(u, v);
		num = 0;
		get_up(u, LCA);
		get_down(v, LCA);
		int cnt = 1;
		for(int i=1; i<=num; i++){
			if(cnt == M+1) break;
			int l = ask[i].l, r = ask[i].r; bool res = ask[i].res;
			if(res){
				vector<int>::iterator it = upper_bound(col[P[cnt]].begin(), col[P[cnt]].end(), r);
				if(it == col[P[cnt]].begin()) continue;
				int s = *(--it);
				if(s < l) continue;
				cnt += Find1(s, l) + 1;
			}
			else{
				vector<int>::iterator it = lower_bound(col[P[cnt]].begin(), col[P[cnt]].end(), l);
				if(it == col[P[cnt]].end()) continue;
				int s = *it;
				if(s > r) continue;
				cnt += Find2(s, r) + 1;
			}
		}
		printf("%d\n", cnt - 1);
	}
	return 0;
} 

(三)结束语

GDOI 2021就这样草草结束了。 离别是如此突然。

zhendelan 发挥较为正常(day1有较大的失误), 但最终还是与省队失之交臂。

MCAdam Day1挂了很多分, 与预期相差许多。

总而言之, 希望两位学长能够在文化课上披荆斩棘, 弥补未能进队的遗憾吧。

还有学姐 lrw04,学长 Conless,

没有你们, 也就没有我一年来在 ss 学习 OI 的美好时光。

无论接下来是走是留,
无论她带给了我多少泪水,
我从未后悔。

--END--


  1. 实际上,可以通过倍增达到 \(\Theta(\log m)\) 的复杂度, 后文会具体阐述。 ↩︎

posted @ 2021-04-15 21:13  __allenge  阅读(223)  评论(0编辑  收藏  举报