题解:AT_arc138_f [ARC138F] KD Tree

· · 题解

一个不一样的做法,不管字典序这种东西。 $f(I_x,I_y)$ 表示只考虑 $x\in I_x$,$y\in I_y$ 的点时能得到的**答案序列**数量,$I$ 代表一个区间。 考虑如何计算 $f([x_l,x_r],[y_l,y_r])$。 先把这里面的点**离散化**一下。然后显然不同的操作序列可能得到相同的答案序列,所以直接做是错的。 那么我们直接容斥,定义 $F(S)$ 为钦定第一步是 $S$ 中任何操作都能到达的答案序列数量,答案即 $\sum\limits_{s\neq \emptyset} (-1)^{|S|-1}F(S)$。 $|S|=1$ 是平凡的,考虑 $|S|=2$ 的情况。 下文说垂直 $x$ 切一刀在 $x_1$ 即在 $x=x_1$ 做一次操作,把点分为 $x\leq x_1$ 和 $x>x_1$ 两部分。 如果垂直 $x$ 轴划两刀在 $x_1<x_2$: - 所有点被划分成 $A|B|C$ 的样子。 - $x_1$ 得到的序列是 $A+BC$,$x_2$ 得到的序列是 $AB+C$。 - 所以答案序列形如 $A+B+C$。 - 方案数就是 $f([x_l,x_1],I_y)\times f((x_1,x_2],I_y)\times f((x_2,x_r],I_y)$。 垂直于 $y$ 轴同理。 如果一刀垂直 $x$ 轴切在 $x_1$,一刀垂直 $y$ 轴切在 $y_1$: - 所有点被划分成 $\frac{C}{A}|\frac{D}{B}$ 四个部分。 - $x_1$ 得到的序列是 $AC+BD$,$x_2$ 得到的序列是 $AB+CD$。 - **容易发现 $B$ 和 $C$ 不能同时不为空**。 - 方案数是 $f([x_l,x_1],[y_l,y_1])f([x_l,x_1],(y_1,y_r])f((x_1,x_r],[y_l,y_1])f((x_1,x_r],(y_1,y_r])$。 ![](https://cdn.luogu.com.cn/upload/image_hosting/uzocyfp1.png) 考虑如何推广到 $|S|>2$。 类似的,如果垂直 $x$ 轴划了 $a$ 刀,垂直 $y$ 轴划了 $b$ 刀,所有点会被划分成 $(a+1)(b+1)$ 部分。我们按照图示方法记录每个部分为一个二元组 $(i,j)$。 根据 $|S|=2$ 的结论,存在至少一个答案序列的条件即**对于任意一个非空部分 $(i_1,j_1)$,所有满足 $i_2<i_1,j_2>j_1$ 和 $i_2>i_1,j_2<j_1$ 的部分都必须是空的**。 上图浅灰色的格子代表该部分非空,它是符合条件的。 这个条件看起来非常好 dp 啊! 设 $g_{x_1,y_0,y_1}$ 表示竖着最后一刀切在 $x_1$,横着最后两刀切在 $y_0$ 和 $y_1$ 的容斥系数。 假设离散化后剩 $m$ 个坐标,有 $f(I_x,I_y)=\sum\limits_{i=0}^{m-1} g_{m,i,m}$。 转移从小到大枚举 $x_1$,再枚举上一刀 $x_0$。开一个辅助转移的数组 $h$。 - 先 $h_{y_0,y_1}\leftarrow -g_{x_0,y_0,y_1}\times f((x_0,x_1],(y_0,y_1])$,代表切一刀竖着的。 - 然后 $h_{y_1,y_2}\leftarrow -h_{y_0,y_1}\times f((x_0,x_1],(y_1,y_2])$,代表切几刀横着的。 记 $c(I_x,I_y)$ 为在范围内的点的数量。为了满足条件且不重不漏,$f_{x_1,y_0,y_1}\leftarrow h_{y_0,y_1}$ 的条件是: - $c((x_0,x_1],(y_0,y_1])>0$,这是为了不重。 - $c((x_0,x_1],(y_1,y_r])=c((x_1,x_r],[y_l,y_0])=0$,这是为了满足条件。 然后就可以转移了。然后这题就做完了。 关于复杂度,往高了算是 $\sum_{i=1}^{n}\sum_{j=1}^n(n-i+1)(n-j+1)i^2j^2$。 这玩意能分析到 $O(n^8)$,但是常数小的没边完全可以过。 代码中使用的变量名和文章中的略有不同。 ::::info[完整代码] ```cpp #include<bits/stdc++.h> #define int long long #define Get(A,B,C,D) f[x[(A)]+1][x[(B)]][y[(C)]+1][y[(D)]] #define Cnt(A,B,C,D) query(x[(A)],x[(B)],y[(C)],y[(D)]) #define CKE if(CHECK) #define FRE if(FIL) using namespace std; const int mod=1000000007,MX=35; const int CHECK=0,FIL=0;int read(); bitset<MX> g[MX][MX][MX]; int n,p[MX],fp[MX],a[MX][MX],f[MX][MX][MX][MX],h[MX][MX][MX],H[MX][MX],Hr[MX]; int query(int xl,int xr,int yl,int yr){ return a[xr][yr]+a[xl][yl]-a[xl][yr]-a[xr][yl];} int solve(int xl,int xr,int yl,int yr){ if(g[xl][xr][yl][yr]) return f[xl][xr][yl][yr]; g[xl][xr][yl][yr]=1; if(query(xl-1,xr,yl-1,yr)<2){return f[xl][xr][yl][yr]=1;} vector<int> x,y; x.emplace_back(xl-1); for(int i=xl;i<=xr;i++) if(yl<=p[i] && p[i]<=yr) x.emplace_back(i); y.emplace_back(yl-1); for(int i=yl;i<=yr;i++) if(xl<=fp[i] && fp[i]<=xr) y.emplace_back(i); int m=x.size()-1; for(int xi=0;xi<m;xi++) for(int xj=xi+1;xj<=m;xj++) for(int yi=0;yi<m;yi++) for(int yj=yi+1;yj<=m;yj++) solve(x[xi]+1,x[xj],y[yi]+1,y[yj]); h[0][0][0]=1; for(int xj=1;xj<=m;xj++){ h[xj][0][0]=0; for(int yi=0;yi<m;yi++) for(int yj=yi+1;yj<=m;yj++) h[xj][yi][yj]=0; for(int xi=0;xi<xj;xi++){ H[0][0]=h[xi][0][0]; for(int yj=1;yj<=m;yj++) Hr[yj]=0; for(int yi=0;yi<m;yi++) for(int yj=yi+1;yj<=m;yj++){ int cnt=h[xi][yi][yj]; if(!yi) cnt-=H[0][0]; else cnt-=Hr[yi]; H[yi][yj]=(cnt%mod)*Get(xi,xj,yi,yj)%mod;Hr[yj]+=H[yi][yj]; if(xi==0 && xj==m && yi==0 && yj==m) H[yi][yj]=0; if(Cnt(xi,xj,yi,yj) && !Cnt(xi,xj,yj,m) && !Cnt(xj,m,0,yi)) h[xj][yi][yj]=(h[xj][yi][yj]+mod-H[yi][yj])%mod; } } } int ans=0; for(int yi=0;yi<m;yi++){ int cnt=h[m][yi][m]; ans=(ans+mod-cnt)%mod; } f[xl][xr][yl][yr]=ans; return f[xl][xr][yl][yr]; } signed main(){ n=read(); for(int i=1;i<=n;i++){p[i]=read();fp[p[i]]=i;} for(int i=1;i<=n;i++) a[i][p[i]]++; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) a[i][j]+=a[i-1][j]; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) a[i][j]+=a[i][j-1]; solve(1,n,1,n); printf("%lld\n",f[1][n][1][n]); return 0; } int read(){ int Ca=0;char Cr=' ';int Cf=1; while(Cr<'0' || Cr>'9'){Cr=getchar();if(Cr=='-'){Cf=-1;}} while(Cr>='0' && Cr<='9'){Ca=Ca*10+Cr-48;Cr=getchar();} return Ca*Cf; } ``` ::::