(笔记)整体二分
整体二分能解决的问题一般具有如下特点:
- 询问的答案具有可二分性。
- 修改对判定答案的贡献互相独立,修改之间互不影响效果。
- 修改如果对判定答案有贡献,则贡献为一确定的与判定标准无关的值。
- 贡献满足交换律,结合律,具有可加性。
- 题目允许使用离线算法。
——许昊然《浅谈数据结构题几个非经典解法》
基本思路
设想对询问答案进行分治,如果每层每个节点都要进行 \(O(n)\) 次操作分治复杂度达到了 \(O(n^2\log n)\) ,还不如不分。反之,如果是每层都只要进行 \(O(n)\) 次操作,那么总体复杂度直接变成了 \(O(n\log n)\),优于上者。整体二分就是采用这种策略将分治时间复杂度简化。
具体地,将每次的询问离线下来,在每个分治点上划分为 \([l,mid]\) 和 \([mid+1,r]\) 两部分,节点内用 \(siz=r-l+1\) 的 \(O(siz)\) 次操作完成划分和答案查询,在 \(l=r\) 时记录答案即可。
例题
P1527 [国家集训队] 矩阵乘法
思路
本题可以采用二维树状数组,每个分治点将 \(\le mid\) 的数赋值为 \(1\) 并根据询问子矩阵内 \(1\) 的个数 \(\geq k\) 或 \(< k\) 划分询问。为什么这样做是对的?
考虑二分过程中的每一层对答案的贡献。
- 对于每一层二分,矩阵中的每个元素最多被加入树状数组一次。
- 对于每一层二分,每个询问只会被处理一次。
- 二分值域的过程中最多只会出现 \(O(\log n)\) 层。
符合我们对整体二分的思路要求。树状数组每层操作数为 \(O(n^2\log^2 n)\) ,查询数为 \(O(q\log^2 n)\),分治层数为 \(O(\log n)\),那么总体时间复杂度为 \(O((n^2+q)\log ^3 n)\),需要注意常数优化。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
const int N=505,M=2.5e5+5;
int n,q,cnt,lsh[M];
int a[N][N];
vector<PII>G[M];
struct Tre{
int av[N][N];
int lowbit(int x){return x&-x;}
void ins(int x,int y,int z){
for(int i=x;i<=n;i+=lowbit(i))
for(int j=y;j<=n;j+=lowbit(j))
av[i][j]+=z;
}
int que(int x,int y){
int res=0;
for(int i=x;i;i-=lowbit(i))
for(int j=y;j;j-=lowbit(j))
res+=av[i][j];
return res;
}
}T;
struct Node{int xa,ya,xb,yb,k,id;};
vector<Node>Q;
int ans[M];
int query(Node x){
int xa=0,ya=0,xb=0,yb=0;
xa=x.xa;ya=x.ya;xb=x.xb;yb=x.yb;
int res=T.que(xb,yb)+T.que(xa-1,ya-1);
res-=T.que(xa-1,yb)+T.que(xb,ya-1);
return res;
}
void fz(int l,int r,vector<Node>&vec){
if(!vec.size())return ;
if(l==r){
for(Node i:vec)ans[i.id]=lsh[l];
return ;
}
int mid=(l+r)>>1;
vector<Node>vec0,vec1;
for(int i=l;i<=mid;i++)
for(PII j:G[i]){
int x=j.first,y=j.second;
T.ins(x,y,1);
}
for(Node i:vec){
int val=query(i);
if(val>=i.k)vec0.push_back(i);
else i.k-=val,vec1.push_back(i);
}
for(int i=l;i<=mid;i++)
for(PII j:G[i]){
int x=j.first,y=j.second;
T.ins(x,y,-1);
}
fz(l,mid,vec0);fz(mid+1,r,vec1);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>q;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
cin>>a[i][j];
lsh[++cnt]=a[i][j];
}
for(int i=1;i<=q;i++){
int xa,ya,xb,yb,k;
cin>>xa>>ya>>xb>>yb>>k;
Q.push_back((Node){xa,ya,xb,yb,k,i});
}
sort(lsh+1,lsh+1+cnt);
cnt=unique(lsh+1,lsh+1+cnt)-(lsh+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
a[i][j]=lower_bound(lsh+1,lsh+1+cnt,a[i][j])-lsh;
G[a[i][j]].push_back(make_pair(i,j));
}
fz(1,cnt,Q);
for(int i=1;i<=q;i++)
cout<<ans[i]<<'\n';
return 0;
}
P11295 [NOISG 2022 Qualification] Dragonfly
部分整体二分需要在二分时预先处理 \([1,mid]\) 这个区间的数据,所以需要在分治时先递归右区间然后删除 \([L,mid]\) 的贡献,再递归左区间,本题就是典例。
注意到每个点能贡献的时间区间一定是一个前缀 \([1,val_i]\) 考虑到先通过整体二分预处理出这个 \(val_i\),然后直接套用离线二维数点计算答案即可。
时间复杂度 \(O(d\log d\log n+d\log n)\)。
由于写法常数实在是太大了,荣获 97pts TLE on Subtask #7,不改了,仅供参考。
UPD:哈哈发现整体二分中间有个 \(\log ^2\) 的查询,直接飙升为 \(O(d\log d\log^2 n)\),现在改好了。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2e5+5,D=2e6+5;
int n,d;
struct Tre{
LL av[N];
int lowbit(int x){return x&-x;}
void ins(int p,LL x){for(int i=p;i<=n;i+=lowbit(i))av[i]+=x;}
LL que(int p){LL res=0;for(int i=p;i;i-=lowbit(i)){res+=av[i];}return res;}
}T;
int b[N],s[N],h[D],val[N];
vector<int>G[N];
int dfn[N],top[N],fat[N],tms;
int son[N],siz[N];
void dfs0(int u,int fa){
fat[u]=fa;
siz[u]=1;
for(int v:G[u]){
if(v==fa)continue;
dfs0(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]])
son[u]=v;
}
}
void dfs1(int u){
dfn[u]=++tms;
int fa=fat[u];
if(son[fa]==u)top[u]=top[fa];
else top[u]=u;
if(son[u])dfs1(son[u]);
for(int v:G[u]){
if(v==son[u]||v==fa)continue;
dfs1(v);
}
}
int query(int x){
int res=0;
while(x){
res+=T.que(dfn[x])-T.que(dfn[top[x]]-1);
x=fat[top[x]];
}
return res;
}
int M[N],A[N],B[N];
void solve(int l,int r,int L,int R){
if(L==R){
for(int i=l;i<=r;i++){
int v=M[i];
val[v]=L;
}
return ;
}
int mid=(L+R)>>1;
for(int i=L;i<=mid;i++)
T.ins(dfn[h[i]],1);
int cnta=0,cntb=0;
for(int i=l;i<=r;i++){
int v=M[i];
if(b[v]-(T.que(dfn[v]+siz[v]-1)-T.que(dfn[v]-1))>0)B[++cntb]=v;
else A[++cnta]=v;
}
for(int i=l;i<=r;i++){
if(i-l+1<=cnta)M[i]=A[i-l+1];
else M[i]=B[i-l-cnta+1];
}
if(cntb)solve(l+cnta,r,mid+1,R);
for(int i=L;i<=mid;i++)
T.ins(dfn[h[i]],-1);
if(cnta)solve(l,l+cnta-1,L,mid);
}
int now[N];
vector<int>opt[D];
void dfs2(int u){
int fa=fat[u],tp=now[s[u]];
if(now[s[u]]){
if(val[tp]<val[u]){
now[s[u]]=u;
opt[val[tp]+1].push_back(u),opt[val[u]+1].push_back(-u);
}
}
else now[s[u]]=u,opt[1].push_back(u),opt[val[u]+1].push_back(-u);
for(int v:G[u]){
if(v==fa)continue;
dfs2(v);
}
if(now[s[u]]==u)now[s[u]]=tp;
}
int main(){
scanf("%d%d",&n,&d);
for(int i=1;i<=n;i++)scanf("%d",&b[i]);
for(int i=1;i<=n;i++)scanf("%d",&s[i]);
for(int i=1;i<=d;i++)scanf("%d",&h[i]);
for(int i=1;i<n;i++){
int u,v;scanf("%d%d",&u,&v);
G[u].push_back(v);
G[v].push_back(u);
}
dfs0(1,0);
dfs1(1);
for(int i=1;i<=n;i++)
M[i]=i;
solve(1,n,1,d);
for(int i=1;i<=n;i++)
if(!b[i])val[i]=0;
dfs2(1);
for(int i=1;i<=d;i++){
for(int v:opt[i]){
if(v>0)T.ins(dfn[v],1);
else T.ins(dfn[-v],-1);
}
printf("%d ",query(h[i]));
}
return 0;
}

浙公网安备 33010602011771号