【学习笔记】根号分治(待补充)

最近有根号分治的题都没那么熟悉,想到了方向感也很差,故写一点题解。

LCA查询

看到题目中的条件 \(\sum k \le 10^5\)

说明 \(k \le S\) 的个数很多,\(S \le k\) 的个数很少。

那么对于前者,考虑一开始最朴素的 \(O(S^2)\) 的暴力,枚举集合中的两个点,求 \(LCA\) 总时间复杂度 \(O( \frac{N}{S} \cdot S^2 \cdot log N)\)

对于后者,若 \(n^2\) 肯定不行。考虑到我们的限制:一共也就 \(n\) 个点,先让 \(A\) 集合里的点向上跳,将点都标起来,若跳到该点已经走过就不用再跑,这样保证了每个点只会被遍历 \(1\) 次。再让 \(B\) 里的点向上跳,若碰到 \(A\) 的标记,进行深度统计,不再向上(应为上面不可能更优)。时间复杂度 \(O(\frac{N}{S} \cdot N)\)

跑起来挺快。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n,m,dep[N];
int siza,a[N];
int sizb,b[N];
int st[N][18];
vector<int> e[N];
void dfs(int u,int fa){
	dep[u] = dep[fa] + 1;
	st[u][0] = fa;
	for(int i = 1;i<=17;++i)st[u][i] = st[st[u][i-1]][i-1];
	for(int v : e[u])if(v ^ fa)dfs(v,u);
}
int LCA(int x,int y){
	if(dep[x] > dep[y])swap(x,y);
	int t = dep[y] - dep[x];
	for(int i = 17;~i;--i)if(t&(1<<i))y = st[y][i];
	if(x == y)return x;
	for(int i = 17;~i;--i)if(st[x][i] ^ st[y][i])
		x = st[x][i], y = st[y][i];
	return st[x][0];
}
int vis[N];
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1,u,v;i<n;++i){
		scanf("%d%d",&u,&v);
		e[u].push_back(v),e[v].push_back(u);
	}
	dfs(1,0);
	
	while(m--){
		scanf("%d",&siza); for(int i = 1;i<=siza;++i)scanf("%d",&a[i]);
		scanf("%d",&sizb); for(int i = 1;i<=sizb;++i)scanf("%d",&b[i]);
		
		int ans = 0;
		if(siza <= 100 && sizb <= 100){
			for(int i = 1;i<=siza;++i)
				for(int j = 1;j<=sizb;++j)
					ans = max(ans,dep[LCA(a[i],b[j])]);
			printf("%d\n",ans);
		}else{
			for(int i = 1;i<=siza;++i){
				int now = a[i];
				while(now){
					if(vis[now] > 0)break;
					vis[now] = 1;
					now = st[now][0];
				}
			}
			for(int i = 1;i<=sizb;++i){
				int now = b[i];
				while(now){
					if(vis[now] > 0){
						if(vis[now] == 1)ans = max(ans,dep[now]);
						break;
					}
					vis[now] = 2;
					now = st[now][0];
				}
			}
			memset(vis,0,sizeof (int)*(n+5));
			printf("%d\n",ans);
		}
	}
	return 0;
}

CF1997E Level Up

同样考虑二元关系。

可以想到随着 \(k\) 的增加,一级一级跳的次数是越来越少的

对于 \(k \le B\),直接暴力模拟

对于 \(k > B\),考虑式子 $ \sum[level \le a[j]] \le k$ 二分下一个升级的位置。

同时 \(sum\) 可能用主席树维护,但根号分治的二元关系又体现了出来。

由于 \(k > B\),故 \(level \le \frac{N}{B}\),直接预处理数组即可。

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int N = 2e5 + 10;
const int B = 440;
int n,q,f[N],a[N];
int sum[450][N];
bool mark[N];
int rk[N];
inline bool check(int l,int r,int level,int k){
	return (r - l + 1) - (sum[level-1][r] - sum[level-1][l-1]) >= k;
}
vector<pii> qry[N];
int ans[N];
int main(){
	scanf("%d%d",&n,&q);
	for(int i = 1;i<=n;++i){
		scanf("%d",&a[i]);
		if(a[i] <= B)++sum[a[i]][i];
	}
	for(int j = 1;j<=B;++j)
		for(int i = 1;i<=n;++i)
			sum[j][i] += sum[j-1][i] + sum[j][i-1] - sum[j-1][i-1];
	for(int i = 1,p,k;i<=q;++i){
		scanf("%d%d",&p,&k);
		qry[k].push_back({p,i});
	}
	for(int k = 1;k<=n;++k){
		if(qry[k].empty())continue;
		if(k <= B){
			int level = 1, cnt = 0;
			for(int i = 1;i<=n;++i){
				mark[i] = 0;
				if(a[i] < level){
					mark[i] = 1;
					continue;
				}
				++cnt;
				if(cnt == k)cnt = 0,++level;
			}
			for(pii p : qry[k])if(!mark[p.first])ans[p.second] = 1;
		}else{
			sort(qry[k].begin(),qry[k].end());
			int pos = 1,level = 1,it = 0;
			while(pos <= n){
				int L = pos, R = n, res = n;
				while(L <= R){
					int mid = L + R >> 1;
					if(check(pos,mid,level,k))res = mid, R = mid - 1;
					else L = mid + 1;
				}
				while(it < qry[k].size() && pos <= qry[k][it].first && qry[k][it].first <= res)
					rk[qry[k][it].first] = level,++it;
				pos = res + 1;
				++level;
			}
			for(pii p : qry[k])ans[p.second] = (a[p.first] >= rk[p.first]);
		}
	}
	for(int i = 1;i<=q;++i)
		puts(ans[i] ? "YES" : "NO");
	return 0;
}

ABC365G

问题陈述

\(N\) 人在办公室工作。

该办公室保存着进出记录,自记录开始以来,已有 \(M\) 人进出。

\(i\)\((1\leq i\leq M)\) 记录由一对整数 \((T_i, P_i)\) 表示,表明在 \(T_i\) 时间, \(P_i\) 如果在办公室外,则进入了办公室,如果在办公室内,则离开了办公室。

已知所有的人在记录开始时都在办公室外。

按以下格式回答 \(Q\) 查询。

对于 \(i\) -th \((1\leq i\leq Q)\) 查询,你会得到一对整数 \((A_i, B_i)\) 。求自记录开始以来,\(A_i\)\(B_i\) 这两个人同时在办公室内的时间段的总长度。

分析

对于 段数 \(\le B\) 的,暴力扫过来统计

对于 段数 \(> B\) 的, 考虑预处理, 这样的 \(i\) 最多有 \(N/B\)

对于每个 \(i\) 预处理出 \(i\) 与其他所有点的结果

最多只可能有 \(\frac{n^2}{B}\) 种状态

预处理时间复杂度 $O(M * N / B) $

本代码已T飞
#include<bits/stdc++.h>
#define se second
#define fi first
using namespace std;
const int N = 2e5 + 10;
typedef pair<int,int> pii;
int n,m;
vector<pii> a,e[N];
int pre[N];
map<pii,int> res;
int sum[N];
bool mark[N];
#define clamp DFJSHSFBSGDRFSRBHBFKBDSJGFDSKDSffhjffghghf
inline int clamp(int l,int r,int x){
	if(x >= r)return r;
	else if(x <= l)return l;
	return x;
}
int query(int x,int y){
	if(x > y)swap(x,y);
	if(mark[x] || mark[y])return res[{x,y}];
	
	if(e[x].empty() || e[y].empty())return res[{x,y}] = 0;
	
	int it = 0, ans = 0;
	for(pii p : e[x]){
		int l = p.fi, r = p.se;
		ans += clamp(l,r,e[y][it].se) - clamp(l,r,e[y][it].fi);
		while(it+1 < e[y].size() && e[y][it+1].fi <= r){
			++it;
			ans += clamp(l,r,e[y][it].se) - clamp(l,r,e[y][it].fi);
		}
	}
	return res[{x,y}] = ans;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1,T,P;i<=m;++i){
		scanf("%d%d",&T,&P);
		a.push_back({T,P});
		if(pre[P])e[P].push_back({pre[P],T}),pre[P] = 0;
		else pre[P] = T;
	}
	const int B = 1000;
	for(int i = 1;i<=n;++i)
		if(e[i].size() >= B){
			mark[i] = 1;
			for(int j = 1;j<=n;++j)sum[j] = 0;
			bool in = 0;
			int now = 0, pt = 0;
			for(pii p : a){
				int T = p.fi, P = p.se;
				if(P == i){
					now += in * (T - pt);
					pt = T; in ^= 1;
				}
				sum[P] = now + in * (T - pt) - sum[P];
			}
			for(int j = 1;j<=n;++i)res[{min(i,j),max(i,j)}] = sum[j];
		}
	int q,x,y; scanf("%d",&q);
	while(q--){
		scanf("%d%d",&x,&y);
		printf("%d\n",query(x,y));
	}
	return 0;
}
posted @ 2024-08-05 19:30  Luzexxi  阅读(31)  评论(0)    收藏  举报