【agc023E】Inversions(线段树,动态规划)

题面

AT

给定\(a_i\),求所有满足\(p_i\le a_i\)的排列\(p\)的逆序对数之和。

题解

首先如何计算排列\(p\)的个数。

设\(cnt[i]\)表示\(a_k\ge i\)的个数,那么满足条件的\(p\)的总数就是\(\prod cnt[i]-(n-i)\)

大概就是从\(n\)开始填数,对于每个数字\(i\)而言,它一共有\(cnt[i]\)个位置可以填,但是后面的数字一共占用了\(n-i\)个位置,所以还剩下\(cnt[i]-(n-i)\)个位置可以填。

先讲讲\(O(n^2log)\)的做法。

枚举任意两个位置\(i,j,i\lt j\),考虑\(i,j\)之间形成逆序对的贡献。

分成三种情况讨论。

1.\(a_i=a_j\)

显然对于任意一种满足条件的方案,要么\(p_i>p_j\)要么\(p_i<p_j\),并且两两对称,所以就是总方案除\(2\)。

2.\(a_i<a_j\)

当\(p_j\)取\([a_i+1,a_j]\)这部分的值的时候,显然不会产生贡献,而当\(p_j\in[1,a_i]\)时,则和上面是一样的,但是注意一下,此时我们直接把\(a_j\)变成了\(a_i\),会对于总方案产生影响,\([a_i+1,a_j]\)这一段的\(cnt\)减小了,所以用一个线段树维护一下区间积,就可以方便的维护当前状态下的总方案,那么这一部分也很好算。

3.\(a_i>a_j\)

很麻烦,但是我们可以正难则反来算,用总方案减去\(p_i<p_j\)的方案数,转化成了第二种情况,也就是将\(a_i\)直接变成\(a_j\)。

这样子用线段树维护,时间复杂度\(O(n^2logn)\),用前后缀之类的东西维护可以做到\(O(n^2)\)。

代码如下:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define MAX 200200
#define MOD 1000000007
#define inv2 500000004
#define lson (now<<1)
#define rson (now<<1|1)
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
int fpow(int a,int b)
{
int s=1;
while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
return s;
}
int n,a[MAX],tot,ans;
int cnt[MAX];
int t[2][MAX<<2];
void Build(int now,int l,int r)
{
if(l==r){t[1][now]=cnt[l]-1-(n-l);t[0][now]=cnt[l]-(n-l);return;}
int mid=(l+r)>>1;
Build(lson,l,mid);Build(rson,mid+1,r);
t[0][now]=1ll*t[0][lson]*t[0][rson]%MOD;
t[1][now]=1ll*t[1][lson]*t[1][rson]%MOD;
}
int Query(int now,int l,int r,int L,int R,int c)
{
if(L<=l&&r<=R)return t[c][now];
int mid=(l+r)>>1,ret=1;
if(L<=mid)ret=1ll*ret*Query(lson,l,mid,L,R,c)%MOD;
if(R>mid)ret=1ll*ret*Query(rson,mid+1,r,L,R,c)%MOD;
return ret;
}
int main()
{
n=read();tot=1;
for(int i=1;i<=n;++i)++cnt[a[i]=read()];
for(int i=n-1;i;--i)cnt[i]+=cnt[i+1];
for(int i=1;i<=n;++i)tot=1ll*tot*(cnt[i]-(n-i))%MOD;
if(!tot){puts("0");return 0;}
Build(1,1,n);
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j)
{
if(a[i]==a[j])ans=(ans+1ll*tot*inv2)%MOD;
else if(a[i]<a[j])
{
int d=1ll*tot*fpow(Query(1,1,n,a[i]+1,a[j],0),MOD-2)%MOD;
d=1ll*d*Query(1,1,n,a[i]+1,a[j],1)%MOD;
ans=(ans+1ll*d*inv2)%MOD;
}
else
{
int d=1ll*tot*fpow(Query(1,1,n,a[j]+1,a[i],0),MOD-2)%MOD;
d=1ll*d*Query(1,1,n,a[j]+1,a[i],1)%MOD;
ans=(ans+MOD-1ll*d*inv2%MOD+tot)%MOD;
}
}
printf("%d\n",ans);
return 0;
}

而这种方法的复杂度瓶颈在于枚举任意两个位置之间逆序对的贡献。

考虑这个能否优化。

首先我们记\(D_i=\frac{cnt[i]-1-(n-i)}{cnt[i]-(n-i)}\),如果要修改某个区间的总方案数,等价于用全局的总方案数乘上某一段区间。设\(S\)为全局总方案数,那么强制求改\(a[i],a[j]\)成一样的总方案数就是:\(S\times \prod_{i=min+1}^{max}D_i\)。这个东西很显然可以写成前缀的形式,也就是\(S=\frac{\prod_{i=1}^{max}D_i}{\prod_{i=1}^{min}D_i}\)。

这样子只需要维护前缀积然后求个和就好了。

注意下\(D_i\)可能为\(0\),所以求的时候要分段计算一下贡献就好了,具体实现见代码。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define MAX 200200
#define MOD 1000000007
#define inv2 500000004
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
int fpow(int a,int b)
{
int s=1;
while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
return s;
}
int n,a[MAX],tot,ans;
int cnt[MAX];
int c1[MAX],c2[MAX];
int lb(int x){return x&(-x);}
void add(int x,int w){while(x<=n)c1[x]=(c1[x]+w)%MOD,++c2[x],x+=lb(x);}
int getsum1(int x){int ret=0;while(x)ret=(ret+c1[x])%MOD,x-=lb(x);return ret;}
int getsum2(int x){int ret=0;while(x)ret+=c2[x],x-=lb(x);return ret;}
int D[MAX],invD[MAX],zero[MAX],S[MAX];
int main()
{
n=read();tot=1;
for(int i=1;i<=n;++i)++cnt[a[i]=read()];
for(int i=n-1;i;--i)cnt[i]+=cnt[i+1];
for(int i=1;i<=n;++i)tot=1ll*tot*(cnt[i]-=(n-i))%MOD;
if(!tot){puts("0");return 0;}
S[0]=D[0]=1;
for(int i=1;i<=n;++i)
{
int x=1ll*(cnt[i]-1)*fpow(cnt[i],MOD-2)%MOD;
if(!x)S[zero[i]=zero[i-1]+1]=i,D[i]=D[i-1];
else zero[i]=zero[i-1],D[i]=1ll*D[i-1]*x%MOD;
invD[i]=fpow(D[i],MOD-2);
}
for(int i=1;i<=n;++i)
{
ans=(ans+1ll*(getsum1(a[i])-getsum1(S[zero[a[i]]]-1)+MOD)*D[a[i]]%MOD*tot%MOD*inv2%MOD)%MOD;
add(a[i],invD[a[i]]);
}
for(int i=1;i<=n;++i)c1[i]=c2[i]=0;
for(int i=n;i;--i)
{
ans-=1ll*(getsum1(a[i]-1)-getsum1(S[zero[a[i]]]-1)+MOD)*D[a[i]]%MOD*tot%MOD*inv2%MOD;
ans=(ans+1ll*getsum2(a[i]-1)*tot)%MOD;ans=(ans+MOD)%MOD;
add(a[i],invD[a[i]]);
}
printf("%d\n",ans);
return 0;
}

【agc023E】Inversions(线段树,动态规划)的更多相关文章

  1. POJ 2374 Fence Obstacle Course(线段树+动态规划)

    Fence Obstacle Course Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 2524   Accepted:  ...

  2. Codeforces 675E Trains and Statistic - 线段树 - 动态规划

    题目传送门 快速的vjudge通道 快速的Codeforces通道 题目大意 有$n$个火车站,第$i$个火车站出售第$i + 1$到第$a_{i}$个火车站的车票,特殊地,第$n$个火车站不出售车票 ...

  3. Codeforces 750E New Year and Old Subsequence - 线段树 - 动态规划

    A string t is called nice if a string "2017" occurs in t as a subsequence but a string &qu ...

  4. ACM学习历程—HDU5696 区间的价值(分治 && RMQ && 线段树 && 动态规划)

    http://acm.hdu.edu.cn/showproblem.php?pid=5696 这是这次百度之星初赛2B的第一题,但是由于正好打省赛,于是便错过了.加上2A的时候差了一题,当时有思路,但 ...

  5. 线段树详解 (原理,实现与应用)(转载自:http://blog.csdn.net/zearot/article/details/48299459)

    原文地址:http://blog.csdn.net/zearot/article/details/48299459(如有侵权,请联系博主,立即删除.) 线段树详解    By 岩之痕 目录: 一:综述 ...

  6. BZOJ_1672_[Usaco2005 Dec]Cleaning Shifts 清理牛棚_动态规划+线段树

    BZOJ_1672_[Usaco2005 Dec]Cleaning Shifts 清理牛棚_动态规划+线段树 题意:  约翰的奶牛们从小娇生惯养,她们无法容忍牛棚里的任何脏东西.约翰发现,如果要使这群 ...

  7. 【arc073f】Many Moves(动态规划,线段树)

    [arc073f]Many Moves(动态规划,线段树) 题面 atcoder 洛谷 题解 设\(f[i][j]\)表示第一个棋子在\(i\),第二个棋子在\(j\)的最小移动代价. 发现在一次移动 ...

  8. 【BZOJ5469】[FJOI2018]领导集团问题(动态规划,线段树合并)

    [BZOJ5469][FJOI2018]领导集团问题(动态规划,线段树合并) 题面 BZOJ 洛谷 题解 题目就是让你在树上找一个最大的点集,使得两个点如果存在祖先关系,那么就要满足祖先的权值要小于等 ...

  9. Codeforces 834D The Bakery - 动态规划 - 线段树

    Some time ago Slastyona the Sweetmaid decided to open her own bakery! She bought required ingredient ...

随机推荐

  1. JUC——线程同步锁(Condition精准控制)

    在进行锁处理的时候还有一个接口:Condition,这个接口可以由用户来自己进行锁的对象创建. Condition的作用是对锁进行更精确的控制. Condition的await()方法相当于Objec ...

  2. Float浮点数的使用和条件

    在这里简单的说一下,我对浮点数的理解,可能说的比较浅,老师也没有说,只是略微的提了一下,完全是我自己个人的理解. 我觉得float浮点数的用法和int的用法有些雷同,浮点数用于计算小数点单位,我们先可 ...

  3. Swing的概述

    Swing在Java中是一个包,下面就是它的基本概述:简介: Java为了方便图形界面的实现, 专门设计了类库来满足各种各样的图形元素和用户交互事件, 该类库即为抽象窗口工具箱(Abstract Wi ...

  4. 配置tensorflow环境(anaconda+jupyter notebook)

    很早之前,tensorflow环境之前我也曾装过,但是用的不是很舒服,很多问题都不明所以然.今天想要系统地学习一下tensorflow,于是又重新搭建了一遍,这次还是踩了不少坑.特此写下此文,供有兴趣 ...

  5. Tesseract 4 自行构建支持双引擎的tessdata 文件

    Tesseract 4 版本具备两种识别引擎:新的基于LSTM(神经网络)引擎与传统引擎.通过在初始化时设定不同的EngineMode启动. OCR Engine modes: 0 Legacy en ...

  6. 2017-2018-2 1723 『Java程序设计』课程 结对编程练习_四则运算 第二周

    一.结对对象 姓名:侯泽洋 学号:20172308 担任角色:驾驶员(侯泽洋) 伙伴第二周博客地址 二.本周内容 (一)继续编写上周未完成代码 1.本周继续编写代码,使代码支持分数类计算 2.相关过程 ...

  7. <<梦断代码>>读后感

    <梦断代码>中对软件工程所面临的种种困难与艰难的描述,即便再过5年读也许都不过时.因为正如原作者所说,书中描写的是一队人马并肩扛起代码大石,虽历经磨难仍欲将其推上山顶的故事,而正是这种故事 ...

  8. Sprint会议3

    昨天:熟悉了一下软件操作,设计了图标. 今天:今天满课,没有做什么实质性的进展. 遇到问题:由于没干什么,也没遇到什么问题.

  9. Task 6.2冲刺会议七 /2015-5-20

    今天把主界面改善了一下,主要功能时摄像头的使用以及语音聊天的部分,两个部分的设计原理基本相同,但是可是好几个模块让有点不知道该怎么下手.这时候我感觉到了模块拼接是很困难的,只有十分清楚每个模块才能很好 ...

  10. 新的Calculator的规范作业

    附加作业题目 第三次作业 mygithub:sonnypp 这是开学来第一次写随笔,这一次的作业是对上一次作业的修改,对于上一次作业,在学长老师的帮助下,我重新修改了下代码,将.h文件分成了一个Sca ...