codeforces1081G Mergesort Strikes Back【期望dp+脑洞】

首先看这样做的特点,就是分到最后小块里的点合并上去的时候相对顺序不变,所以先加上块内逆序对的期望
合并的时候一定是一边卡住一个大值,另一边跳指针,所以把一个值向右直到有大于它的值位置的一段区间看作一段
当前合并两块合并到第i个和第j个,如果i和j都是块的开头就一定不会构成逆序对,因为双指针的时候会直接比较这两个点,其他情况有1/2的概率,所以成为一对逆序对的概率是\( \frac{i+j-2}{2(i+j)} \)
又因为最小快的长度最多两种,所以求前缀和然后枚举i直接求一排j的值即可

#include<iostream>
#include<cstdio>
#include<map>
using namespace std;
const int N=100005;
int n,k,mod,ans,inv[N],s[N];
map<int,int>c;
int ksm(int a,int b)
{
	int r=1;
	while(b)
	{
		if(b&1)
			r=1ll*r*a%mod;
		a=1ll*a*a%mod;
		b>>=1;
	}
	return r;
}
void pre(int l,int r,int k)
{
	if(k<=1||l==r)
	{
		c[r-l+1]++;
		return;
	}
	int mid=(l+r)>>1;
	pre(l,mid,k-1);
	pre(mid+1,r,k-1);
}
int clc(int x,int y)
{
	int r=1ll*x*y%mod;
	for(int i=1;i<=x;i++)
		r=(r-2ll*(s[i+y]-s[i])%mod)%mod;
	return r;
}
int main()
{
	scanf("%d%d%d",&n,&k,&mod);
	for(int i=1;i<=1e5;i++)
		inv[i]=ksm(i,mod-2),s[i]=(s[i-1]+inv[i])%mod;
	pre(1,n,k);
	for(map<int,int>::iterator i=c.begin();i!=c.end();i++)
	{
		ans=(ans+1ll*i->first*(i->first-1)%mod*inv[2]%mod*i->second%mod)%mod;
		ans=(ans+1ll*i->second*(i->second-1)%mod*inv[2]%mod*clc(i->first,i->first)%mod)%mod;
	}
	for(map<int,int>::iterator i=c.begin();i!=c.end();i++)
		for(map<int,int>::iterator j=c.begin();j!=c.end();j++)
			if(i->first<j->first)
				ans=(ans+1ll*clc(i->first,j->first)*i->second%mod*j->second%mod)%mod;
	printf("%lld\n",(1ll*ans*inv[2]%mod+mod)%mod);
	return 0;
}
posted @ 2019-06-17 17:00  lokiii  阅读(...)  评论(...编辑  收藏