1. 程式人生 > >bzoj1078: [SCOI2008]斜堆

bzoj1078: [SCOI2008]斜堆

題目
題解

考慮斜堆中最後插入的那個結點,容易發現:
(1)它一定是一個極左結點(就是從根往它的路上一直都是沿著左鏈走),因為插入的時候每次都是插入到左子樹中;
(2)它一定木有右子樹,因為插入的時候每次都是把原來的某棵子樹作為新結點的左子樹;
滿足(1)(2)的結點可能有多個,但緊接著可以發現,這個斜堆中的每個結點如果木有左子結點,那麼也木有右子結點(或者說,每個非葉結點都有左子樹),而在插入一個結點之前,其所有的祖先都被交換了左右子樹,所以,若新結點的祖先中有滿足(1)(2)的,且新結點不是葉結點,那麼在新結點插入之前,這個滿足(1)(2)的祖先必然是隻有右子樹而木有左子樹的,這與上面的那個性質矛盾,所以,可以得出:最後插入的那個結點一定是滿足(1)(2)的結點中,深度最小的那個(設為X),除非X的左子結點是葉結點,此時為了滿足字典序最小,應該取X的左子結點為最後插入的。找到這個最後插入的結點以後,只需要把它刪掉,並把它的所有祖先交換左右子樹,就是插入該結點以前的狀態了。這樣可以找到字典序最小的插入順序。

#include<bits/stdc++.h>
using namespace std;
int fa[52],ls[52],rs[52],n,i,st[52],top,x,rt;
inline char gc(){
	static char buf[100000],*p1=buf,*p2=buf;
	return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int rd(){
	int x=0,fl=1;char ch=gc();
	for (;ch<48||ch>
57;ch=gc())if(ch=='-')fl=-1; for (;48<=ch&&ch<=57;ch=gc())x=(x<<3)+(x<<1)+(ch^48); return x*fl; } inline void wri(int a){if(a>=10)wri(a/10);putchar(a%10|48);} void solve(){ int x=rt; while (rs[x]!=-1) x=ls[x]; int t=ls[x]; if (t!=-1 && ls[t]==-1 && rs[t]
==-1) x=t; st[++top]=x; if (x==rt) rt=ls[x]; if (fa[x]!=-1) fa[ls[x]]=fa[x],ls[fa[x]]=ls[x]; x=fa[x]; while (x!=-1) swap(ls[x],rs[x]),x=fa[x]; } int main(){ n=rd(); fa[0]=-1; memset(ls,-1,sizeof(ls)); memset(rs,-1,sizeof(rs)); for (i=1;i<=n;i++){ x=rd(); if (x<100) ls[x]=i,fa[i]=x; else rs[x-100]=i,fa[i]=x-100; } for (i=1;i<=n+1;i++) solve(); wri(st[top]); for (i=top-1;i;i--) putchar(' '),wri(st[i]); }