loj2163 / bzoj2212 / P3521 [POI2011]ROT-Tree Rotations(线段树合并)
P3521 [POI2011]ROT-Tree Rotations
loj2163 [POI2011]ROT-Tree Rotations(数据加强)
(loj的数据套了个fread优化才过...)
显然地,对于一棵线段树(树根设为$rt$),是否翻转它的子树的子树,对于跨$mid$的逆序对数量没有影响。
那么我们可以层层统计(设左右子树为$lc,rc$):
不翻转时,该层(跨$mid$)的逆序对:$a[a[a[rt].lc].rc].sum*a[a[a[rt].rc].lc].sum$
翻转时,逆序对数量:$a[a[a[rt].lc].lc].sum*a[a[a[rt].rc].rc].sum$
递归处理即可。
重点是合并线段树:
前提:两棵动态开点线段树
实现(将树$pr$合并到$o$上):
void merge(int &o,int pr){ if(!o||!pr) {o=o+pr;return;}//一棵为空则返回另一边 a[o].sum+=a[pr].sum; .......//结算信息 merge(a[o].lc,a[pr].lc); merge(a[o].rc,a[pr].rc); //递归合并 }
蓝后就结束了。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cctype> 5 #define re register 6 using namespace std; 7 typedef long long ll; 8 char gc(){ 9 static char buf[100000],*p1=buf,*p2=buf; 10 return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++; 11 } 12 void read(int &x){ 13 char c=gc();x=0; 14 while(!isdigit(c)) c=gc(); 15 while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=gc(); 16 }//以上读入优化 17 ll min(ll &a,ll &b){return a<b?a:b;} 18 struct node{int sum,lc,rc;}a[4000005]; 19 int n,u,rt; ll res1,res2,ans; 20 void update(int &o,int l,int r,int v){ 21 if(!o) o=++u; 22 ++a[o].sum; 23 if(l==r) return; 24 int mid=l+((r-l)>>1); 25 if(v<=mid) update(a[o].lc,l,mid,v); 26 else update(a[o].rc,mid+1,r,v); 27 } 28 void merge(int &o,int pr){//把o/pr当作左/右子树,合并到左子树 29 if(!o||!pr) {o=o+pr;return;} 30 a[o].sum+=a[pr].sum; 31 res1+=1ll*a[a[o].lc].sum*a[a[pr].rc].sum; //翻转:lc->lc 和 rc->rc 之间的逆序对数 32 res2+=1ll*a[a[o].rc].sum*a[a[pr].lc].sum; //不翻转:lc->rc 和 rc->lc 之间的逆序对数 33 merge(a[o].lc,a[pr].lc); //合并线段树,并计算 lc->lc 和 rc->lc 之间的逆序对数 34 merge(a[o].rc,a[pr].rc); //同上 35 } 36 void dfs(int &x){//题意的神奇递归输入 37 int q,lc,rc; read(q); 38 if(!q){ 39 dfs(lc); dfs(rc); 40 res1=res2=0; 41 merge(x=lc,rc);//合并 42 ans+=min(res1,res2);//选代价小的 43 }else update(x=0,1,n,q);//给叶子结点单独建一棵线段树,后面再合并 44 } 45 int main(){ 46 // freopen("P3521_2.in","r",stdin); 47 read(n); dfs(rt); 48 printf("%lld",ans); 49 return 0; 50 }