CF1361F Johnny and New Toy 和 BZOJ2212 Tree Rotations 和 PKUWC2018 Minimax
Johnny and New Toy
Johnny has a new toy. As you may guess, it is a little bit extraordinary. The toy is a permutation \(P\) of numbers from \(1\) to \(n\), written in one row next to each other.
For each \(i\) from \(1\) to \(n - 1\) between \(P_i\) and \(P_{i + 1}\) there is a weight \(W_i\) written, and those weights form a permutation of numbers from \(1\) to \(n - 1\). There are also extra weights \(W_0 = W_n = 0\).
The instruction defines subsegment \([L, R]\) as good if \(W_{L - 1} < W_i\) and \(W_R < W_i\) for any \(i\) in \(\{L, L + 1, \ldots, R - 1\}\). For such subsegment it also defines \(W_M\) as minimum of set \(\{W_L, W_{L + 1}, \ldots, W_{R - 1}\}\).
Now the fun begins. In one move, the player can choose one of the good subsegments, cut it into \([L, M]\) and \([M + 1, R]\) and swap the two parts. More precisely, before one move the chosen subsegment of our toy looks like:
and afterwards it looks like this:
Such a move can be performed multiple times (possibly zero), and the goal is to achieve the minimum number of inversions in \(P\).
Johnny's younger sister Megan thinks that the rules are too complicated, so she wants to test her brother by choosing some pair of indices \(X\) and \(Y\), and swapping \(P_X\) and \(P_Y\) (\(X\) might be equal \(Y\)). After each sister's swap, Johnny wonders, what is the minimal number of inversions that he can achieve, starting with current \(P\) and making legal moves?
You can assume that the input is generated randomly. \(P\) and \(W\) were chosen independently and equiprobably over all permutations; also, Megan's requests were chosen independently and equiprobably over all pairs of indices.
\(2 \leq n \leq 2 \cdot 10^5,1 \leq q \leq 5 \cdot 10^4\)
Tutorial (en)
Let us start with an analysis of good subsegments for the fixed permutation. The whole permutation is a good subsegment itself, as \(W_0 = W_n = 0 < W_k\) for any \(k \in [1, 2, \ldots, n - 1]\). If we denote the minimal weight in \(W_1, \ldots, W_{n - 1}\) as \(W_m\), then we can notice that subsegments \([1, m]\) and \([m + 1, n]\) contain all good subsegments except the whole permutation. As a result, we can recursively find all good subsegments by recursive calls in \([1, m]\) and \([m + 1, n]\). We can view the structure of good subsegments as a binary tree.
Example structure of tree for \(P = [3, 4, 6, 2, 1, 5]\) and \(W = [5, 2, 3, 1, 4]\):
Now we want to analyze the possible moves for players. It turns out that the player's move is equivalent to choosing a vertex of a tree and swapping its left and right subtree. Notice that moves made in different vertices influence disjoint pairs of elements, so in some sense these moves are independent if we are interested only in the number of inversions. This observation allows us to find a simple method for calculating the result. For each vertex, calculate the number of inversions between the left and right subtree. Using these numbers for each vertex, we can find out whether we want to swap its subtrees or not, so the result can be calculated by a simple loop over all vertices.
From randomness of the input, we can deduce that the tree we built has height \(\mathcal{O}(\log n)\). We can calculate the number of inversions easily if in each vertex we keep a structure with elements from permutation contained in that vertex. Such structure must support querying the number of elements smaller than some \(x\). The shortest implementation uses the ordered set, but any BST can do it (segment tree needs some squeezing to fit into ML).
The above solution works fast if there are no queries. We can view each request as two removals and additions of elements. If so, we can notice that each query modifies the number of inversions in at most \(\mathcal{O}(\log n)\) vertices. So all we need to do is update the number of inversions in these vertices and recalculate the global result.
Building a tree and calculating initial number of inversions takes \(\mathcal{O}(n \log n)\) or \(\mathcal{O}(n \log^2n)\), answering each query cost \(\mathcal{O}(\log^2 n)\), so the final complexity is \(\mathcal{O}(n \log n + q \log^2 n)\).
CO int N=2e5+10;
int in[N],weight[N];
int64 ans;
// cartesian tree node
// while creating we calculate set of elements and number of inversions
struct node{
int splitter;
int64 invs;
ordered_set S;
node*left,*right;
node():splitter(0),invs(0),left(0),right(0) {}
}*root;
// add/delete contribution from given node to result
IN void add_result(node*cur,int mt){
if(cur->splitter==0) return;
ans+=mt*min(cur->invs,(int64)cur->left->S.size()*(int64)cur->right->S.size()-cur->invs); // swaping
}
// Create cartesian tree
// Because expected depth is O(logn) we can use bruteforce to find the smallest weight
node*get_cartesian(int from,int to){
node*ret=new node();
if(from==to){
ret->S.insert(in[from]);
return ret;
}
int id=from;
for(int i=from+1;i<to;++i)if(weight[i]<weight[id]) id=i;
ret->splitter=id;
ret->left=get_cartesian(from,id);
ret->right=get_cartesian(id+1,to);
// get elements from ordered set
ret->S=ret->right->S;
for(int v:ret->left->S){
ret->S.insert(v);
ret->invs+=ret->right->S.order_of_key(v);
}
// add contribution to the result
add_result(ret,1);
return ret;
}
// depending on mt, removes/add element val on position p
void update_cart(int p,int val,int mt){
node*cur=root;
while(cur->splitter){
add_result(cur,-1);
if(mt==-1) cur->S.erase(val);
else cur->S.insert(val);
if(p<=cur->splitter){
cur->invs+=mt*cur->right->S.order_of_key(val);
cur=cur->left;
}
else{
cur->invs+=mt*(cur->left->S.size()-cur->left->S.order_of_key(val));
cur=cur->right;
}
}
if(mt==-1) cur->S.erase(val);
else cur->S.insert(val);
cur=root;
while(cur->splitter){
add_result(cur,1);
if(p<=cur->splitter) cur=cur->left;
else cur=cur->right;
}
}
int main(){
int n=read<int>();
for(int i=1;i<=n;++i) read(in[i]);
for(int i=1;i<n;++i) read(weight[i]);
root=get_cartesian(1,n);
for(int m=read<int>();m--;printf("%lld\n",ans)){
int x=read<int>(),y=read<int>();
if(x==y) continue;
// remove both numbers
update_cart(x,in[x],-1);
update_cart(y,in[y],-1);
swap(in[x],in[y]);
// add both numbers
update_cart(x,in[x],1);
update_cart(y,in[y],1);
}
return 0;
}
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\) 的权值为:
-
若 \(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<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;
}