[AGC023E] Inversions

tag:组合计数,线段树


第一反应是枚举俩位置计算贡献,然后推推式子优化。

首先先考虑一共有多少种合法的排列,把 \(a_i\) 排序后(设为 \(\{b\}\)

\[ans=\prod_{i=1}^nb_i-i+1 \]


枚举两个位置 \(i,\ j(i<j)\),第一反应是令 \(a_i,a_j\) 都为 \(\min(a_i,a_j)\),然后算合法的排列数除以 \(2\)。然而naive了,只有 \(a_i<a_j\) 时是正确的。当 \(a_i>a_j\) 时算出来的是顺序对的个数,所以用总方案数减一下即可。

\(a_i<a_j\),二者在 \(b\) 序列中的位置为 \(c_i,\ c_j\),则贡献为:

\[\frac12ans\cdot\frac{a_i-c_i}{a_j-c_j+1}\cdot\prod_{k=c_i+1}^{c_j-1}\frac{b_k-k}{b_k-k+1} \]

其实就是把原贡献除掉,再乘上新的贡献。\(a_i>a_j\) 时的贡献就是用 \(ans-\)上式。


分两部分 \(ans\cdot\frac1{2(a_j-c_j+1)},\ (a_i-b_i)\cdot\prod_{k=c_i+1}^{c_j-1}\frac{b_k-k}{b_k-k+1}\)

考虑从小到大加入 \(a_i\),则保证当前的 \(a_i\ge\) 之前加入的 \(a_j\)

所以对于下标小于 \(i\) 的部分贡献为正,下标大于 \(i\) 的部分贡献为负且都要加上一个 \(ans\)

于是用以下标为下标的线段树去维护一下区间和就行了,然后再拿个数据结构维护下标大于 \(i\) 的元素个数。

更新的时候全局乘 \(\frac{a_i-b_i}{a_i-b_i+1}\),然后把 \(i\) 处的值改为 \(a_i-b_i\)。(这样做的话,点 \(j\) 的值就正好对应着 \(\prod\cdots\)


#include<bits/stdc++.h>
using namespace std;

template<typename T>
inline void Read(T &n){
	char ch; bool flag=false;
	while(!isdigit(ch=getchar()))if(ch=='-')flag=true;
	for(n=ch^48;isdigit(ch=getchar());n=(n<<1)+(n<<3)+(ch^48));
	if(flag)n=-n;
}

enum{
	MAXN = 1000005,
	MOD = 1000000007
};

inline int ksm(int base, int k=MOD-2){
	int res = 1;
	while(k){
		if(k&1)
			res = 1ll*res*base%MOD;
		base = 1ll*base*base%MOD;
		k >>= 1;
	}
	return res;
}

inline int dec(int a, int b){
	a -= b;
	if(a<0) a += MOD;
	return a;
}

inline int inc(int a, int b){
	a += b;
	if(a>=MOD) a -= MOD;
	return a;
}

inline void iinc(int &a, int b){a = inc(a,b);}
inline void ddec(int &a, int b){a = dec(a,b);}
inline void upd(int &a, long long b){a = (a+b)%MOD;}

int n, fn;

struct ele{
	int val, id;
	inline bool operator <(const ele &k)const{return val<k.val;}
}a[MAXN];

struct node{
	int mul, sum;
	#define mul(x) t[x].mul
	#define sum(x) t[x].sum
}t[MAXN<<2];
inline int lc(int x){return x<<1;}
inline int rc(int x){return x<<1|1;}

inline void Push_Up(int x){
	sum(x) = inc(sum(lc(x)),sum(rc(x)));
}

inline void Mul(int x, int mul){
	mul(x) = 1ll*mul(x)*mul%MOD;
	sum(x) = 1ll*sum(x)*mul%MOD;
}

inline void Push_Down(int x){
	if(mul(x)!=1)
		Mul(lc(x),mul(x)),
		Mul(rc(x),mul(x)),
		mul(x) = 1;
}

void Mul(int x, int head, int tail, int l, int r, int mul){
	if(l<=head and tail<=r) return Mul(x,mul);
	Push_Down(x);
	int mid = head+tail >> 1;
	if(l<=mid) Mul(lc(x),head,mid,l,r,mul);
	if(mid<r) Mul(rc(x),mid+1,tail,l,r,mul);
	Push_Up(x);
}

void Update(int x, int head, int tail, int loc, int k){
	if(head==tail) return sum(x)=k, void();
	Push_Down(x);
	int mid = head+tail >> 1;
	if(loc<=mid) Update(lc(x),head,mid,loc,k);
	if(mid<loc) Update(rc(x),mid+1,tail,loc,k);
	Push_Up(x);
}

int Query(int x, int head, int tail, int l, int r){
	if(l<=head and tail<=r) return sum(x);
	Push_Down(x);
	int mid = head+tail >> 1, res=0;
	if(l<=mid) iinc(res,Query(lc(x),head,mid,l,r));
	if(mid<r) iinc(res,Query(rc(x),mid+1,tail,l,r));
	return res;
}

namespace BIT{
	#define lowbit(x) (x&-x)
	int t[MAXN];
	inline void Add(int x, int k){for(;x<=n;x+=lowbit(x))t[x]+=k;}
	inline int Query(int x){int res=0;for(;x;x-=lowbit(x))res+=t[x];return res;}
	inline int Query(int l, int r){return Query(r)-Query(l-1);}
	#undef lowbit
}

void Build(int x, int head, int tail){
	mul(x) = 1;
	if(head==tail) return;
	int mid = head+tail >> 1;
	Build(lc(x),head,mid); Build(rc(x),mid+1,tail);
}

int main(){
	Read(n);
	for(register int i=1; i<=n; i++) Read(a[i].val), a[i].id = i;
	sort(a+1,a+n+1);
	fn = 1;
	for(register int i=1; i<=n; i++) fn = 1ll*fn*(a[i].val-i+1)%MOD;
	Build(1,1,n);
	int ans=0;
	for(register int i=1; i<=n; i++){
		int tmp=0;
		if(a[i].id>1) iinc(tmp,Query(1,1,n,1,a[i].id-1));
		if(a[i].id<n) ddec(tmp,Query(1,1,n,a[i].id+1,n));
		tmp = 1ll*ksm(2ll*(a[i].val-i+1)%MOD)*tmp%MOD;
		iinc(tmp,BIT::Query(a[i].id+1,n));
		upd(ans,1ll*tmp*fn);
		BIT::Add(a[i].id,1);
		Mul(1,1ll*(a[i].val-i)*ksm(a[i].val-i+1)%MOD);
		Update(1,1,n,a[i].id,a[i].val-i);
	}
	cout<<ans<<'\n';
	return 0;
}
posted @ 2021-06-25 09:27  oisdoaiu  阅读(63)  评论(0编辑  收藏  举报