【学习笔记】根号分治(待补充)
最近有根号分治的题都没那么熟悉,想到了方向感也很差,故写一点题解。
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;
}

浙公网安备 33010602011771号