Tree Rotations
现在有一棵二叉树,所有非叶子节点都有两个孩子。在每个叶子节点上有一个权值(有n个叶子节点,满足这些权值为1..n的一个排列)。可以任意交换每个非叶子节点的左右孩子。
要求进行一系列交换,使得最终所有叶子节点的权值按照遍历序写出来,逆序对个数最少。
\(1 \leq n \leq 200000\)
分析
左儿子和右儿子内部的逆序对是不会相互影响的,将两部分单独处理再考虑合并。
合并的时候需要判断左儿子在前还是右儿子在前。枚举这两种操作取最优值。
每个节点的逆序对是左子树的逆序对的数量+右子树的逆序对数量+跨越子树的逆序对数量
我们交换子树更改的就是最后那个跨越子树的逆序对数量,那么:
-
如果我们交换了左右子树,跨越子树的逆序对数量为没交换时左子树中
=mid的数的数量 -
如果我们没有交换左右子树,那么逆序对数量就是左子树中>=mid的数的数量*右子树中
两个都求出来之后取个最小值即可。
然后仔细考虑每一个数,发现它的逆序对被一种二分分治的方式计算出来了。
然后我们向上传递信息。这一步用线段树合并即可。
时间复杂度\(O(N \log N)\)
const int MAXT=2e5*20;
int n;
int a[MAXT]; // edit 1
int r,ls[MAXT],rs[MAXT],idx;
void init(int&x)
{
x=++idx;
read(a[x]);
if(a[x])
return;
init(ls[x]);
init(rs[x]);
}
ll ans1,ans2,ans;
int root[MAXT],sz;
struct PreSegTree
{
int L[MAXT],R[MAXT];
int sum[MAXT];
void pushup(int now)
{
sum[now]=sum[L[now]]+sum[R[now]];
}
void insert(int&now,int l,int r,int p)
{
if(!now)
now=++sz;
if(l==r)
{
sum[now]=1;
return;
}
int mid=(l+r)>>1;
if(p<=mid)
insert(L[now],l,mid,p);
else
insert(R[now],mid+1,r,p);
pushup(now);
}
int merge(int x,int y)
{
if(!x||!y)
return x+y;
ans1+=(ll)sum[R[x]]*sum[L[y]];
ans2+=(ll)sum[L[x]]*sum[R[y]];
L[x]=merge(L[x],L[y]);
R[x]=merge(R[x],R[y]);
pushup(x);
return x;
}
}T;
void solve(int x)
{
if(a[x])
return;
solve(ls[x]);
solve(rs[x]);
ans1=ans2=0;
root[x]=T.merge(root[ls[x]],root[rs[x]]);
ans+=min(ans1,ans2);
}
int main()
{
read(n);
init(r);
for(int i=1;i<=idx;++i)
if(a[i])
T.insert(root[i],1,n,a[i]);
solve(r);
printf("%lld\n",ans);
return 0;
}
根据该二叉树的性质,跟题干中的树有关的数组都应该开到2n。
Minimax
小 \(C\) 有一棵 \(n\) 个结点的有根树,根是 \(1\) 号结点,且每个结点最多有两个子结点。
定义结点 \(x\) 的权值为:
-
若 \(x\) 没有子结点,那么它的权值会在输入里给出,保证这类点中每个结点的权值互不相同。
-
若 \(x\) 有子结点,那么它的权值有 \(p_x\) 的概率是它的子结点的权值的最大值,有 \(1-p_x\) 的概率是它的子结点的权值的最小值。
现在小 \(C\) 想知道,假设 \(1\) 号结点的权值有 \(m\) 种可能性,权值第 \(i\) 小的可能性的权值是 \(V_i\),它的概率为 \(D_i(D_i>0)\),求:
你需要输出答案对 \(998244353\) 取模的值。
对于所有数据,满足 \(0 < p_i \cdot 10000 < 10000\),所以易证明所有叶子的权值都有概率被根取到。
题解
https://www.luogu.com.cn/blog/Isaunoya/solution-p5298
好妙的一个题…
我们设 \(f_{i,j}\) 为 \(i\) 节点出现 \(j\) 的概率
设 \(l = \text{ch}[i][0] , r = \text{ch}[i][1]\) 即左儿子右儿子
设 \(m\) 为叶子结点的个数
显然, \(i\) 出现 \(j\) 的概率为
不难发现,这个柿子有关前缀和和后缀和,可以用线段树合并的操作来进行转移,从下到上转移,求出根节点的概率就好了…
时间复杂度\(O(n\log n)\)。
这样分析复杂度:一个管辖区间为\([l,r]\)的区间最多被操作\(r-l+1\)次。
CO int N=3e5+10;
int fa[N],ch[N][2],cnt[N];
int val[N];
vector all;
int root[N],tot;
int lc[N*20],rc[N*20],sum[N*20],tag[N*20];
int prob[N];
#define mid ((l+r)>>1)
IN void push_up(int x){
sum[x]=add(sum[lc[x]],sum[rc[x]]);
}
IN void put_tag(int x,int v){
sum[x]=mul(sum[x],v),tag[x]=mul(tag[x],v);
}
IN void push_down(int x){
if(tag[x]!=1){
if(lc[x]) put_tag(lc[x],tag[x]);
if(rc[x]) put_tag(rc[x],tag[x]);
tag[x]=1;
}
}
void insert(int&x,int l,int r,int p,int v){
x=++tot,sum[x]=v,tag[x]=1;
if(l==r) return;
if(p<=mid) insert(lc[x],l,mid,p,v);
else insert(rc[x],mid+1,r,p,v);
}
int merge(int x,int y,int l,int r,int tx,int ty,CO int v){
if(!x and !y) return 0;
if(!x) {put_tag(y,ty); return y;}
if(!y) {put_tag(x,tx); return x;}
push_down(x),push_down(y);
int slcx=sum[lc[x]],srcx=sum[rc[x]],slcy=sum[lc[y]],srcy=sum[rc[y]]; // edit 1
lc[x]=merge(lc[x],lc[y],l,mid,add(tx,mul(srcy,1+mod-v)),
add(ty,mul(srcx,1+mod-v)),v);
rc[x]=merge(rc[x],rc[y],mid+1,r,add(tx,mul(slcy,v)),
add(ty,mul(slcx,v)),v);
push_up(x);
return x;
}
void clear(int x,int l,int r){
if(!x) return;
if(l==r) {prob[l]=sum[x]; return;}
push_down(x);
clear(lc[x],l,mid);
clear(rc[x],mid+1,r);
}
#undef mid
void dfs(int x){
if(cnt[x]==0){
insert(root[x],1,all.size(),val[x],1);
}
else if(cnt[x]==1){
dfs(ch[x][0]);
root[x]=root[ch[x][0]];
}
else{
dfs(ch[x][0]),dfs(ch[x][1]);
root[x]=merge(root[ch[x][0]],root[ch[x][1]],1,all.size(),0,0,val[x]);
}
}
int main(){
int n=read();
for(int i=1;i<=n;++i)
if(read(fa[i])) ch[fa[i]][cnt[fa[i]]++]=i;
for(int i=1;i<=n;++i) read(val[i]);
for(int i=1;i<=n;++i){
if(cnt[i]) val[i]=mul(val[i],i10000);
else all.push_back(val[i]);
}
sort(all.begin(),all.end());
for(int i=1;i<=n;++i)if(cnt[i]==0)
val[i]=lower_bound(all.begin(),all.end(),val[i])-all.begin()+1;
dfs(1);
clear(root[1],1,all.size());
int ans=0;
for(int i=1;i<=(int)all.size();++i)
ans=add(ans,mul(mul(i,all[i-1]),mul(prob[i],prob[i])));
printf("%d\n",ans);
return 0;
}