P3521 [POI2011]ROT-Tree Rotations
分析
我们看操作,我们可以将任意一个节点的左右子树交换。
则,我们是改变的一段序列的顺序。
这里类似于的归并排序求逆序对的过程。对于某一个节点,我们在考虑交换与否左右子树会产生的最少新逆序对是多少时,只与左右子树中的数字有关,而与其数字的具体的相对关系无关了。
例如,某节点的左子树为1,4
,右子树3,2
,其实右子树的3,2
我们根本不关心,我们只在乎其中有哪些数字,左子树1,4
,右子树2,3
。则不交换产生的新的逆序对为2
,交换了之后,也是2
。
我们直接从叶节点上升过程中,维护一个权值线段树,其中维护区间内有的值的数量和,直接进行线段树合并。
但是合并过程中,我们需要计算,不交换时产生的新的逆序对ans1,交换时产生的新的逆序对ans2。在合并过程中,我们需要维护两个值cnt1/2
,为对于当前区间[l,r]
而言,左/右子树有多少个点在该区间前,即为左/右子树在[1,l-1]
有多少个点。
落实到具体的合并操作中即为。
- 某个区间只有左子树有,而右子树没有,则
ans1+=tr[u].cnt*cnt2
- 同理,某个区间只有右子树有,而左子树没有,则
ans2+=tr[v].cnt*cnt1
- 接下来到叶节点后,我们直接两个都加,
ans2+=tr[v].cnt*cnt1
,ans1+=tr[u].cnt*cnt2
。
其中有个小错误,坑了我一手。就是我们合并的时候,通常是直接将第二个线段树合并到第一个线段树上。但是这样会影响我们的参数传递,因为有时左子树已经变了。可能说的不是太清楚,直接看代码。
int merge(int u,int v,int l,int r,int cnt1,int cnt2)
{
// cout<<l<<" "<<r<<" "<<cnt1<<" "<<cnt2<<endl;
if(!u)
{
ans2 += 1ll*tr[v].cnt*cnt1;
return v;
}
if(!v)
{
ans1 += 1ll*tr[u].cnt*cnt2;
return u;
}
if(l==r)
{
ans1 += 1ll*tr[u].cnt*cnt2;ans2 += 1ll*tr[v].cnt*cnt1;
tr[u].cnt += tr[v].cnt;
return u;
}
int mid = l + r >> 1;
tr[u].r = merge(tr[u].r,tr[v].r,mid+1,r,cnt1 + tr[tr[u].l].cnt,cnt2 + tr[tr[v].l].cnt);//就是这里,如果跟下面的反过来的话,则传入的参数就错了
tr[u].l = merge(tr[u].l,tr[v].l,l,mid,cnt1,cnt2);
pushup(u);
return u;
}
AC_code
#include<bits/stdc++.h>
#define ios ios::sync_with_stdio(false); cin.tie(0), cout.tie(0)
using namespace std;
using ll = long long;
const int N = 2e5 + 10;
struct Node
{
int l,r;
int cnt;
}tr[N*30];
int n,idx,cnt;
ll ans,ans1,ans2;
int root[N*2];
void pushup(int u)
{
tr[u].cnt = tr[tr[u].l].cnt + tr[tr[u].r].cnt;
}
void modify(int &u,int l,int r,int x)
{
if(!u) u = ++idx;
if(l==r)
{
tr[u].cnt ++;
return ;
}
int mid = l + r >> 1;
if(x<=mid) modify(tr[u].l,l,mid,x);
else modify(tr[u].r,mid+1,r,x);
pushup(u);
}
int merge(int u,int v,int l,int r,int cnt1,int cnt2)
{
// cout<<l<<" "<<r<<" "<<cnt1<<" "<<cnt2<<endl;
if(!u)
{
ans2 += 1ll*tr[v].cnt*cnt1;
return v;
}
if(!v)
{
ans1 += 1ll*tr[u].cnt*cnt2;
return u;
}
if(l==r)
{
ans1 += 1ll*tr[u].cnt*cnt2;ans2 += 1ll*tr[v].cnt*cnt1;
tr[u].cnt += tr[v].cnt;
return u;
}
int mid = l + r >> 1;
tr[u].r = merge(tr[u].r,tr[v].r,mid+1,r,cnt1 + tr[tr[u].l].cnt,cnt2 + tr[tr[v].l].cnt);
tr[u].l = merge(tr[u].l,tr[v].l,l,mid,cnt1,cnt2);
pushup(u);
return u;
}
void dfs(int u)
{
int x;cin>>x;
if(!x)
{
int l = ++cnt;dfs(l);
int r = ++cnt;dfs(r);
ans1 = 0,ans2 = 0;
root[u] = merge(root[l],root[r],1,n,0,0);
// cout<<ans1<<" "<<ans2<<'\n';
ans += min(ans1,ans2);
}
else modify(root[u],1,n,x);
}
int main()
{
ios;
cin>>n;
cnt = 1;
dfs(cnt);
cout<<ans<<'\n';
return 0;
}
标签:cnt,右子,int,tr,POI2011,Tree,cnt2,cnt1,ROT
From: https://www.cnblogs.com/aitejiu/p/16860556.html