深黯Solution

先对于 \(n=4\) 打表所有的排列,然后观察规律。

考虑经典套路拆逆序对贡献为每一位的分别贡献。(后面比它小的数)

给个贡献的表感受一下(左边排列右边贡献):

1 2 3 4|0 0 0 0
1 2 4 3|0 0 1 0
1 3 2 4|0 1 0 0
1 3 4 2|0 1 1 0
1 4 2 3|0 2 0 0
1 4 3 2|0 2 1 0
2 1 3 4|1 0 0 0
2 1 4 3|1 0 1 0

然后我们发现对于第 \(i\) 位的贡献的 “循环节” 是一个下限为 \(0\),上限为 \(n-i\),公差为 \(1\) 的等差数列但是每个数字会重复 \((n-i)!\) 次。

还有更神奇的事情,当 \(i+1\) 的贡献循环一次时,\(i\) 的贡献加一!

于是考虑将原数组 \(a\) 的逆序对用树状数组/线段树处理后从右向左考虑贡献,记录 \(i+1\) 位的第一次为 \(0\) 的地方,对于最前边和最后面不是 “循环节” 的地方暴力加答案,“循环节” 直接用等差数列求即可。

复杂度上,对于 \((n-i)>20\)\(i\),贡献最多会改变一次(\(20!>10^{18}\)),因此复杂度为 \(O(n\log n+20^2)\)

@No0chenquanlin 让补的证明:

性质1:然后我们发现对于第 \(i\) 位的贡献的 “循环节” 是一个下限为 \(0\),上限为 \(n-i\),公差为 \(1\) 的等差数列但是每个数字会重复 \((n-i)!\) 次。

  1. 我们注意到,对于一个位置后面的所有数字的交换不会改变这一位的贡献,所以每个数字会重复 \((n-i)!\) 次。

  2. \(i-1\) 位改变后,\(i\) 位和它后面的位的大小关系肯定是形如 \(1,2,3,4,5\) 的,所以 \(i\) 位会清 \(0\)

  3. \(i\) 位的数被交换到后面时,必然要交换一个比它大 \(1\) 的数上来(因为字典序要变成比目前字典序大的最小字典序),于是贡献会加一。

性质2:还有更神奇的事情,当 \(i+1\) 的贡献循环一次时,\(i\) 的贡献加一!

前面的第 1 条可得。

#include<bits/stdc++.h>
#define int __int128
using namespace std;
namespace kong{bool st;}
namespace zhu{
struct{
	int l,r,sum,lazy;
}tr[2002000];
#define ls (id<<1)
#define rs (id<<1|1)
#define l(x) tr[x].l
#define r(x) tr[x].r
#define sm(x) tr[x].sum
#define lz(x) tr[x].lazy
#define mid ((l(id)+r(id))>>1)
#define up sm(id)=sm(ls)+sm(rs)
#define dw\
	sm(ls)+=lz(id)*(r(ls)-l(ls)+1);\
	sm(rs)+=lz(id)*(r(rs)-l(rs)+1);\
	lz(ls)+=lz(id);\
	lz(rs)+=lz(id);\
	lz(id)=0;
void build(int id,int l,int r){
	l(id)=l,r(id)=r;
	if(l==r) return;
	build(ls,l,mid);
	build(rs,mid+1,r);
}
void add(int id,int l,int r,int y){
	if(l(id)>=l&&r(id)<=r){
		sm(id)+=(r(id)-l(id)+1)*y;
		lz(id)+=y;
		return;
	}
	dw;
	if(l<=mid){
		add(ls,l,r,y);
	}
	if(r>mid){
		add(rs,l,r,y);
	}
	up;
}
int que(int id,int x){
	if(l(id)==r(id)){
		return sm(id);
	}
	dw;
	if(x<=mid){
		return que(ls,x);
	}
	else{
		return que(rs,x);
	}
}
#undef sm
int n,a[500500],k,mod,sum[30],st[500500];
int sm(int x){
	if(x<=20) return sum[x];
	else return 2e18;
}
string main(){
	freopen("army.in","r",stdin);
	freopen("army.out","w",stdout); 
	long long t1,t2,t3;
	cin>>t1>>t2>>t3;
	n=t1,k=t2,mod=t3;
	sum[0]=1;
	for(int i=1;i<=20;i++){
		sum[i]=sum[i-1]*i;
	}
	for(int i=1;i<=n;i++){
		cin>>t1;
		a[i]=t1;
	}
	build(1,1,n);
	for(int i=n;i>=1;i--){
		st[i]=que(1,a[i]);
		add(1,a[i]+1,n,1);
	}
	int lstbh=1,ans=0;
	for(int i=n-1;i>=1;i--){
		if(!lstbh){
			ans=(ans+k*st[i]%mod)%mod;
			continue;
		}
		int t=k,now=st[i]+1,mx=n-i,j=lstbh,twc=sm(n-i);
//		cout<<"---------\n";
//		cout<<i<<'\n';
//		cout<<st[i]<<' '<<mx<<' '<<lstbh<<'\n';
		ans=(ans+lstbh*st[i]%mod)%mod;
		t-=lstbh;
//		cout<<"初始    :"<<ans<<" "<<t<<'\n';
		while(now<=mx&&t>=twc){
			j+=twc;
			t-=twc;
			ans=(ans+twc*now%mod)%mod;
			now++;
		}
//		cout<<"前端杂碎:"<<ans<<" "<<t<<'\n';
		if(now<=mx){
			lstbh=0;
			ans=(ans+t*now%mod)%mod;
//			cout<<"死";
			continue;
		}
		lstbh=j;
		int xhc=sm(n-i+1);
		ans=(ans+(t/xhc)*twc%mod*((0+mx)*(mx+1)/2%mod)%mod)%mod;
		t=t%xhc;
//		cout<<"循环    :"<<ans<<" "<<t<<'\n';
		now=0;
		while(t>=twc){
			t-=twc;
			ans=(ans+twc*now%mod)%mod;
			now++;
		}
		ans=(ans+t*now%mod)%mod;
//		cout<<"尾端杂碎:"<<ans<<" "<<t<<'\n';
	}
	long long out=(ans%mod+mod)%mod;
	cout<<out<<'\n';
	return "last";
}
}
namespace kong{bool ed;double MB(){return (&st-&ed)/1048576.0;}}
signed main(){
	cin.tie(0);cout.tie(0);
	ios::sync_with_stdio(0);
	cerr<<zhu::main()<<'\n'<<kong::MB()<<'\n';
}
/*
5 9 1000000007
5 2 4 3 1 

*/
posted @ 2025-08-15 16:51  LEWISAK  阅读(34)  评论(1)    收藏  举报