BZOJ2212 Tree Rotations 和 PKUWC2018 Minimax

Tree Rotations

现在有一棵二叉树,所有非叶子节点都有两个孩子。在每个叶子节点上有一个权值(有n个叶子节点,满足这些权值为1..n的一个排列)。可以任意交换每个非叶子节点的左右孩子。

要求进行一系列交换,使得最终所有叶子节点的权值按照遍历序写出来,逆序对个数最少。

\(1 \leq n \leq 200000\)

分析

左儿子和右儿子内部的逆序对是不会相互影响的,将两部分单独处理再考虑合并。

合并的时候需要判断左儿子在前还是右儿子在前。枚举这两种操作取最优值。

每个节点的逆序对是左子树的逆序对的数量+右子树的逆序对数量+跨越子树的逆序对数量

我们交换子树更改的就是最后那个跨越子树的逆序对数量,那么:

  • 如果我们交换了左右子树,跨越子树的逆序对数量为没交换时左子树中<mid的数的数量*右子树中>=mid的数的数量

  • 如果我们没有交换左右子树,那么逆序对数量就是左子树中>=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\) 的权值为:

  1. \(x\) 没有子结点,那么它的权值会在输入里给出,保证这类点中每个结点的权值互不相同

  2. \(x\) 有子结点,那么它的权值有 \(p_x\) 的概率是它的子结点的权值的最大值,有 \(1-p_x\) 的概率是它的子结点的权值的最小值。

现在小 \(C\) 想知道,假设 \(1\) 号结点的权值有 \(m\) 种可能性,权值第 \(i\)的可能性的权值是 \(V_i\),它的概率为 \(D_i(D_i>0)\),求:

\[\sum_{i=1}^{m}i\cdot V_i\cdot D_i^2 \]

你需要输出答案对 \(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\) 的概率为

\[f_{i,j} = f_{l,j} * (p_i \sum_{k=1}^{j-1}f_{r,k} + (1-p_i)\sum_{k=j+1}^{m}f_{r,k}) + f_{r,j} * (p_i \sum_{k=1}^{j-1}f_{l,k} + (1-p_i)\sum_{k=j+1}^{m}f_{l,k}) \]

不难发现,这个柿子有关前缀和和后缀和,可以用线段树合并的操作来进行转移,从下到上转移,求出根节点的概率就好了…

时间复杂度\(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<int> 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<int>();
	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;
}

猜你喜欢

转载自www.cnblogs.com/autoint/p/9636227.html
今日推荐