线段树合并
学了发线段树合并。
对于一棵子树,无论它是否交换,都不会影响到这棵子树外的其它节点。那么我们只要每一次取小的那个就好了。
对每个节点开一棵权值线段树,每次把左右儿子合并上来。而左右儿子有两个贡献,分别为一个儿子左区间的数个数*另一个儿子右区间的数个数,对这两个取min即可。
代码:
#include<cctype>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 400005
#define F inline
using namespace std;
typedef long long LL;
struct tree{ int l,r; LL s; }t[N<<4];
int n,m,nd,a[N],to[N][2],rt[N];
LL ans,L,R;
F char readc(){
static char buf[100000],*l=buf,*r=buf;
if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
return l==r?EOF:*l++;
}
F int _read(){
int x=0; char ch=readc();
while (!isdigit(ch)) ch=readc();
while (isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=readc();
return x;
}
void dfs1(int &x){
if (!(a[x=++nd]=_read()))
dfs1(to[x][0]),dfs1(to[x][1]);
}
F void pshp(int x){ t[x].s=t[t[x].l].s+t[t[x].r].s; }
void mdfy(int &x,int l,int r,int p){
x=++nd; int mid=l+r>>1;
if (l==r) return void(t[x].s=1);
if (p<=mid) mdfy(t[x].l,l,mid,p);
else mdfy(t[x].r,mid+1,r,p); pshp(x);
}
int mrg(int x,int y){
if (!x||!y) return x+y;
L+=t[t[x].r].s*t[t[y].l].s;
R+=t[t[x].l].s*t[t[y].r].s;
t[x].l=mrg(t[x].l,t[y].l);
t[x].r=mrg(t[x].r,t[y].r);
return pshp(x),x;
}
LL dfs(int x){
LL ret=0;
if (!a[x]){
ret=dfs(to[x][0])+dfs(to[x][1]),L=R=0;
rt[x]=mrg(rt[to[x][0]],rt[to[x][1]]),ret+=min(L,R);
}
else mdfy(rt[x],1,n,a[x]); return ret;
}
int main(){ return n=_read(),dfs1(m),nd=0,printf("%lld\n",dfs(1)),0; }