bitset 求解高维偏序
菜,题简单,trick 蠢,求别骂。
记录今天做题的时候遇到的一个小 trick。
先看一道题:P3810 【模板】三维偏序(陌上花开)。
平凡的三维偏序板子,相信大家都会用 CDQ/树套树/K-D tree 之类的优秀做法秒了吧!
然后看这个题:求五维偏序,\(n\le 3\times 10^4\),保证每一维这 \(n\) 个数都是 \(n\) 的排列。
我会套三层 CDQ 做到 \(\mathcal{O}(n\log^4n)\)!我还会 K-D tree 做到 \(\mathcal{O}(n^{\frac{7}{4}})\)!
然后看这个题:CF1826E. Walk the Runway。
题意有点复杂,但是就是需要求 \(m\) 维偏序,\(m\le 500\),\(n\le 5000\),保证每一维都 \(\le n\)。
显然 CDQ 并不可取,如果直接暴力也是 \(\mathcal{O}(n^2m)\) 的,K-D tree 应该也不行(我不会)。怎么办?这里介绍一种使用 bitset 的简单做法。
有一个 \(n\times n\) 的矩阵 \(A\),\(A_{i,j}\) 为 1 表示 \(j\) 每一维都严格小于 \(i\)。枚举每一维 \(j\),排一遍序可以求出在这一维上严格小于第 \(i\) 个物品的集合,记为 \(S_{i,j}\)。显然 \(A_{i,j}\) 为 1 当且仅当 \(\forall k\in[1,m]\) 都有 \(j\in S_{i,k}\),而这个过程其实就是 bitset 求并。于是我们就做到了时间复杂度 \(\mathcal{O}(mn\log n+\dfrac{mn^2}{w})\),空间复杂度 \(\mathcal{O}(\dfrac{n^2}{w})\) 求 \(m\) 维偏序。
还可以优化吗?
考虑这道题:\(m\) 维偏序,\(n\le 5\times 10^4\),保证每一维这 \(n\) 个数都是 \(n\) 的排列,空间 64MB。
空间爆了,怎么办?考虑分块优化空间。我们对于每一维进行值域上的分块,块长 \(b=\sqrt{n}\)。定义 \(f_{i,j}\) 表示第 \(i\) 维,这一维的值落在值域上 \([1,j\times b]\) 这一区间的点集,\(g_{i,j}\) 表示第 \(i\) 维值为 \(j\) 的点。显然这个可以 \(\mathcal{O}(mn\sqrt{n})\) 预处理。询问的时候依旧是整块取现成,暴力遍历散块。
这样做的话时间复杂度依旧是 \(\mathcal{O}(mn\sqrt{n}+\dfrac{mn^2}{w})\),但是空间优化到了 \(\mathcal{O}(\dfrac{mn\sqrt{n}}{w})\)(注意这道题可以这样做是因为保证每一维这 \(n\) 个数都是 \(n\) 的排列,如果只是像上一道题一样保证 \(\le n\) 则复杂度并不正确)。
附一个 CF1826E 的代码。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,nxt;
}e[25000005];
int tot,head[5005],deg[5005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot,deg[v]++;
}
int n,m,b[5005],p[5005];ll a[5005],f[5005];
int cmp(int x,int y){
return b[x]<b[y];
}
bitset<5005>res[5005];
void solve(){
m=read(),n=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(i!=j)res[i][j]=1;
for(int j=1;j<=m;j++){
for(int i=1;i<=n;i++)b[i]=read(),p[i]=i;
sort(p+1,p+n+1,cmp);bitset<5005>tmp;
for(int i=1;i<=n;i++){
int pos=i;while(pos<=n&&b[p[pos]]==b[p[i]])res[p[pos]]&=tmp,pos++;
pos--;for(int k=i;k<=pos;k++)tmp[p[k]]=1;
i=pos;
}
}
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(res[i][j])add(i,j);
queue<int>q;for(int i=1;i<=n;i++)if(!deg[i])q.push(i),f[i]=a[i];
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;f[v]=max(f[v],f[u]+a[v]);
if((--deg[v])==0)q.push(v);
}
}
ll ans=0;
for(int i=1;i<=n;i++)ans=max(ans,f[i]);
printf("%lld\n",ans);
}
signed main(){
int T=1;
while(T--){
solve();
}
return 0;
}
reference:几道很Interesting的偏序问题 - yyb,bitset 求解高维偏序。