CF749E Inversions After Shuffle 解题报告 (期望 树状数组)

E. Inversions After Shuffle

题意

有一个长 \(n\) 的排列, 随机选取一段区间进行随机全排列, 求排列后整个序列的逆序对期望个数.

\((n \le 10^5)\).


思路

首先, 考虑一整个排列进行排序后的逆序对期望个数,

一共有 \(\frac{n(n-1)}{2}\) 对点, 每对点行程逆序对的概率为 \(\frac{1}{2}\) , 所以逆序对期望个数为 \(\frac{n(n-1)}{4}\).


那么, 我们就可以算出每个区间对答案的贡献,

长度为 \(len\) 的区间有 \(n-len+1\) 个, 它们排序后的逆序对期望个数为 \(\frac{len(len-1)}{4}\), 所以总贡献为
\[
\sum_{len=1}^{n} \frac{len(len-1)}{4}\times(n-len+1)
\]

然后, 我们再考虑在选定区间外的逆序对个数,

用 \((i,j)\) 来表示一对逆序对, \([l,r]\) 表示当前选中的区间.


先考虑 \(i \not \in [l,r]\) 的情况, 设 \(i=k\) 时的逆序对个数为 \(pir[k]\), 那么贡献为
\[
\sum_{i=1}^{l-1} pir[i] + \sum_{i=r+1}^{n} pir[i]
\]
考虑 \(pir[i]\) 怎么算,

我们可以从小到大枚举排列中的每个数 \(i\), 在它的位置, 设为 \(pl[i]\) 上 +1, 然后在树状数组上查询 \([pl[i]+1,n]\) 的区间和,

因为我们是从小到大枚举的每个数, 所以这时查询到的都是比 \(i\) 小的数, 也就是 \(i\) 为左端点的逆序对, 即 \(pir[i]\).


再考虑 \(i \in [l,r],\ j >r\) 的情况, 直接算貌似不太好算, 考虑容斥,

先不考虑 \(j>r\) 的限制, 则贡献为 \(pir[i] \times i \times (n-i+1)\),

我们再考虑每个满足 \(a[k]<a[i]\) 且 \(k>i\) 的 \(k\), 它会导致 \(i\) 多算了 \(1 \times i \times (n-k+1)\) 的贡献, 因为当 \(j \ge k\) 时, \(k\) 本不应该有贡献的,

所以, 对于每个 \(a[i]\), 我们在枚举到它的时候, 先在 \([1,i-1]\) 打上 \((n-i+1)\) 的标记, 表示到时候要减去的权值,

然后再查询 \(i\) 的标记, 设为 \(val[i]\), 它对答案的总贡献就是
\[
pir[i] \times i \times (n-i+1) - val[i] \times i
\]

这样, 我们就得到了选取每个区间时, 所得到的逆序对的总期望个数, 最后 \(ans/=\frac{n(n+1)}{2}\) 即可. (总共有 \(\frac{n(n+1)}{2}\) 个区间.)


代码

#include<bits/stdc++.h>
#define ll long long
#define db double
using namespace std;
const int _=1e5+7;
int n,a[_],pl[_]; // c0 用来算 pir, c1 用来算多余的贡献
ll c[2][_],pir[_],sum[_];
// 先算出后缀和, 再算出后缀和的后缀和.
db ans;
void add(int x,ll w,bool id){
  for(int i=x;i<=n;i+=i&(-i))
    c[id][i]+=w;
}
void modify(int l,int r,ll w,bool id){
  add(l,w,id);
  add(r+1,-w,id);
}
ll Sum(int x,bool id){
  ll res=0;
  for(int i=x;i;i-=i&(-i))
    res+=c[id][i];
  return res;
}
ll query(int l,int r,bool id){
  return Sum(r,id)-Sum(l-1,id);
}
void init(){
  cin>>n;
  for(int i=1;i<=n;i++){
    scanf("%d",&a[i]);
    pl[a[i]]=i;
  }
  for(int i=1;i<=n;i++){
    pir[pl[i]]=query(pl[i],n,0);
    add(pl[i],1ll,0);
    modify(1,pl[i]-1,n-pl[i]+1,1ll);
    ll val=Sum(pl[i],1);
    ans+=(db)pl[i]*pir[pl[i]]*(n-pl[i]+1)-(db)val*pl[i];
  }
}
void run(){
  for(int i=n;i>=1;i--)
    sum[i]=sum[i+1]+pir[i];
  for(int i=n;i>=1;i--)
    sum[i]=sum[i+1]+sum[i];
  for(db i=1;i<=n;i+=1)
    ans+=i*(i-1)/4*(n-i+1);
  ll res=0;
  for(int i=1;i<=n;i++){
    res+=pir[i-1];
    ans+=sum[i+1]+(n-i+1)*res;
  }

  ans/=(db)n*(n+1)/2;
}
int main(){
  init();
  run();
  printf("%.9lf\n",ans);
  return 0;
}

原文地址:https://www.cnblogs.com/brucew-07/p/12189372.html

时间: 2024-08-26 21:05:24

CF749E Inversions After Shuffle 解题报告 (期望 树状数组)的相关文章

Dynamic Inversions II 逆序数的性质 树状数组求逆序数

Dynamic Inversions II Time Limit: 6000/3000MS (Java/Others) Memory Limit: 128000/64000KB (Java/Others) SubmitStatus Problem Description 给出N个数a[1],a[2] ... a[N],a[1]...a[N]是1-N的一个排列,即1 <= a[i] <= N且每个数都不相同.有M个操作,每个操作给出x,y两个数,你将a[x],a[y]交换,然后求交换后数组的逆序

hdu 5101 Select(树状数组)

题目链接:hdu5101 Select 题目大意:N和K,给定若干组数,要从从不同组中选出连个数和大于K,问说有多少种组成方案. 解题思路:树状数组维护,将所有的数离散化掉对应成树状数组的下标,每次先计算一组,然后再将该组的元素插入到 树状数组中. #include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; const int m

hdu 1556 Color the ball(树状数组)

转载请注明出处:http://blog.csdn.net/u012860063 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1556 Problem Description N个气球排成一排,从左到右依次编号为1,2,3....N.每次给定2个整数a b(a <= b),lele便为骑上他的"小飞鸽"牌电动车从气球a开始到气球b依次给每个气球涂一次颜色.但是N次以后lele已经忘记了第I个气球已经涂过几次颜色了,你能帮他算出每个气

bnu 51636 Squared Permutation(树状数组)(北师16校赛)

最近,无聊的过河船同学在玩一种奇怪的名为"小Q的恶作剧"的纸牌游戏. 现在过河船同学手有张牌,分别写着,打乱顺序之后排成一行,位置从左往右按照标号. 接下来小Q同学会给出个操作,分为以下两种: 1.给定,交换从左往右数的第和第张牌, 2.给定,对从左往右数的第张牌,记下位置是这张牌上的数字的牌的数字,询问所有记下的数字加起来的结果. 虽然无聊的过河船同学精通四则运算,但是要完成这么大的计算量还是太辛苦了,希望你能帮他处理这些操作. Input 第一行是一个正整数,表示测试数据的组数,

hdu4325 树状数组+离散化

http://acm.hdu.edu.cn/showproblem.php?pid=4325 Problem Description As is known to all, the blooming time and duration varies between different kinds of flowers. Now there is a garden planted full of flowers. The gardener wants to know how many flower

Playrix Codescapes Cup (Codeforces Round #413, rated, Div. 1 + Div. 2) C. Fountains 【树状数组维护区间最大值】

题目传送门:http://codeforces.com/contest/799/problem/C C. Fountains time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standard output Arkady plays Gardenscapes a lot. Arkady wants to build two new fountains. The

HDU 1556 数据结构-树状数组-改段求点

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1556 解题思路:树状数组,只要了解树状数组的原理就不用死记模板了,总之树状数组管理的就是前缀和,高度越高的的结点管理的范围越广 所以要是改点求段:更改一个点就要向上传递 所以要是改段求点:更改一个点就要向下传递 代码: #include <cstdio> #include <iostream> #include <cstring> using namespace std;

[BZOJ3065]带插入区间K小值 解题报告 替罪羊树+值域线段树

刚了一天的题终于切掉了,数据结构题的代码真**难调,这是我做过的第一道树套树题,做完后感觉对树套树都有阴影了......下面写一下做题记录. Portal Gun:[BZOJ3065]带插入区间k小值. 这道题的题面其实都提醒怎么做了,维护区间k小值用值域线段树,但要维护一个插入操作,树状数组套主席树也用不了,那么这道题还剩下平衡树可以搞,那就上平衡树吧. 我这里的做法,因为要维护序列的顺序,所以我这里用到替罪羊树套值域线段树:我们在替罪羊树的每个节点都套一颗值域线段树,记录以该节点为根的子树的

Dynamic Inversions 50个树状数组

Dynamic Inversions Time Limit: 30000/15000MS (Java/Others) Memory Limit: 128000/64000KB (Java/Others) SubmitStatus Problem Description 给出N个数a[1],a[2] ... a[N],有M个操作,每个操作给出x,y两个数,你将a[x],a[y]交换,然后求交换后数组的逆序对个数.逆序对的意思是1 <= i < j <= N 且a[i] > a[j].