PQ-Tree 学习笔记
为什么 NOIP 模拟会考到这种东西啊?
PQ-Tree 能解决也恐怕仅能解决如下问题:
对于长度为 \(n\) 的排列 \(p\),在线地给出 \(q\) 个限制,每次给定一个集合 \(S\),要求在 \(p\) 中,\(S\) 中的所有数出现位置构成一个连续段,要求对排列计数。
朴素实现的 PQ-Tree 可以给出一个 \(O(nq)\) 的做法,理论上可以做到 \(O(n+\sum |S|)\)。
具体地,我们手玩每次将两个限制 \(S_1,S_2\) 合并,发现形成了三个新的限制 \(S_1/S_2,S_2/S_1,S_1\cap S_2\),而且这三个集合出现位置必须相邻,而且 \(S_1\cap S_2\) 必须在中间。
我们继续玩下去,相当于对于每两个不平凡相交的限制 \(S_1,S_2\)(即 \(S_1\not\subset S_2,S_2\not \subset S_1,S_1\cap S_2\neq \emptyset\))可以去分裂这两个限制,直到限制变成了一个树形结构。我们将原排列的数全部放在叶子节点,对该树求一种 dfs 序就可以得到所有可能的 \(p\)。对于非节点,要么该节点的所有儿子节点都可以在 dfs 序中以任意顺序遍历(称其为 P 点),要么只能顺着或者倒着遍历它的儿子序列(称其为 Q 点)。
如何加入限制并修改整颗树呢?我们将限制中要求的数对应的叶子结点标成黑色。对于非叶子如果子树中有黑有白就是灰色,全黑是黑色,全白是白色。
我们根据黑白灰的颜色分布修改树的形态。分讨有点多,可以看 OI-wiki,这里不讲了。
namespace PQTree{
void assert(bool x){if(!x){puts("No Solution");exit(0);}}
const int N=200003;
vi vec[N];
bool pq[N];
int stk[N],tp,cnt;
int apply(){
if(tp) return stk[tp--];
return ++cnt;
}
void crush(int x){
vec[x].clear();
stk[++tp]=x;
}
bool tag[N];
int col[N];
void paint(int u){
if(u<=n){col[u]=tag[u]+1;return;}
col[u]=0;
for(int v:vec[u]){paint(v);col[u]|=col[v];}
}
int seq[N],rk;
void split(int u){
if(col[u]!=3){seq[++rk]=u;return;}
int c[4]={0,0,0,0};
for(int v:vec[u]) ++c[col[v]];
assert(c[3]<=1);
if(pq[u]){
int las=0;
bool fl=1;
for(int v:vec[u]){
int c=col[v];
if(c>1) c^=1;
if(las>c){fl=0;break;}
las=c;
}
if(fl){
for(int v:vec[u])
if(col[v]==3) split(v);
else seq[++rk]=v;
return;
}
reverse(vec[u].begin(),vec[u].end());
fl=1;las=0;
for(int v:vec[u]){
int c=col[v];
if(c>1) c^=1;
if(las>c){fl=0;break;}
las=c;
}
assert(fl);
for(int v:vec[u])
if(col[v]==3) split(v);
else seq[++rk]=v;
}
else{
if(c[1]>1){
int x=apply();pq[x]=0;
for(int v:vec[u])
if(col[v]==1) vec[x].emplace_back(v);
seq[++rk]=x;
}
else if(c[1]){for(int v:vec[u]) if(col[v]==1) seq[++rk]=v;}
if(c[3]){for(int v:vec[u]) if(col[v]==3) split(v);}
if(c[2]>1){
int x=apply();pq[x]=0;
for(int v:vec[u])
if(col[v]==2) vec[x].emplace_back(v);
seq[++rk]=x;
}
else if(c[2]){for(int v:vec[u]) if(col[v]==2) seq[++rk]=v;}
}
crush(u);
}
void solve(int u){
if(col[u]!=3) return;
int c[4]={0,0,0,0};
for(int v:vec[u]) ++c[col[v]];
assert(c[3]<=2);
if(!c[2]&&c[3]==1){
for(int v:vec[u])
if(col[v]==3) return solve(v);
}
if(pq[u]){
int l=0,r=vec[u].size()-1;
while(col[vec[u][l]]==1) ++l;
while(col[vec[u][r]]==1) --r;
for(int i=l+1;i<r;++i) assert(col[vec[u][i]]==2);
vi tmp;
int cc=0;
for(int v:vec[u]){
if(col[v]==3){
split(v);
if(cc) reverse(seq+1,seq+rk+1);
for(int i=1;i<=rk;++i) tmp.emplace_back(seq[i]);
rk=0;
}
else tmp.emplace_back(v);
if(col[v]!=1) cc=1;
}
vec[u].swap(tmp);
}
else{
vi tmp;
for(int v:vec[u])
if(col[v]==1) tmp.emplace_back(v);
if(c[3]){
int x=apply();pq[x]=1;
for(int v:vec[u])
if(col[v]==3){
split(v);
for(int i=1;i<=rk;++i) vec[x].emplace_back(seq[i]);
rk=0;break;
}
if(c[2]>1){
int y=apply();pq[y]=0;
for(int v:vec[u])
if(col[v]==2) vec[y].emplace_back(v);
vec[x].emplace_back(y);
}
else if(c[2]){for(int v:vec[u]) if(col[v]==2) vec[x].emplace_back(v);}
if(c[3]>1){
bool ff=0;
for(int v:vec[u])
if(col[v]==3){
if(ff){
split(v);
for(int i=rk;i;--i) vec[x].emplace_back(seq[i]);
rk=0;break;
}
ff=1;
}
}
tmp.emplace_back(x);
}
else{
if(c[2]>1){
int x=apply();pq[x]=0;
for(int v:vec[u])
if(col[v]==2) vec[x].emplace_back(v);
tmp.emplace_back(x);
}
else if(c[2]){for(int v:vec[u]) if(col[v]==2) tmp.emplace_back(v);}
}
if(tmp.size()==1lu){
int x=tmp.back();
vec[u]=vec[x];
pq[u]=pq[x];
crush(x);
}
else vec[u].swap(tmp);
}
}
void opt(){
paint(n+1);solve(n+1);
for(int i=1;i<=n;++i) tag[i]=0;
}
void init(){
pq[cnt=n+1]=0;
for(int i=1;i<=n;++i)
vec[n+1].emplace_back(i);
}
void clear(){
for(int i=1;i<=cnt;++i) vec[i].clear();
cnt=tp=0;
}
/*
* int calc(int u){
* if(u<=n) return 1;
* int res;
* if(pq[u]) res=2;
* else res=fac[vec[u].size()];
* for(int v:vec[u]) res=(ll)res*calc(v)%P;
* return res;
* }
* void show(int u){
* if(u<=n){
* printf("Leaf %d\n",u);
* return;
* }
* printf("%c Node %d: ",pq[u]?'Q':'P',u);
* for(int v:vec[u]) printf("%d ",v);
* putchar('\n');
* for(int v:vec[u]) show(v);
* }
* void gen(int u){
* if(u<=n){lis[++tt]=u;return;}
* for(int v:vec[u]) gen(v);
* }
*/
}