1. 程式人生 > >【JZOJ A組】氣泡排序

【JZOJ A組】氣泡排序

Description

題目背景 在這裡插入圖片描述 氣泡排序的交換次數被定義為交換過程的執行次數。

題面描述 小 S 開始專注於研究⻓度為 n 的排列,他想知道,在你運氣足夠好的情況下(即每次氣泡排序的交換次數都是可能的最少交換次數,彷彿有上帝之手在操控),對於一個等概率隨機的長度為n 的排列,進行這樣的氣泡排序的期望交換次數是多少?

Input

從檔案 inverse.in 中讀入資料。 輸入第一行包含一個正整數 T ,表示資料組數。 對於每組資料,第一行有一個正整數,保證 n ≤ 10^7 。

Output

輸出到檔案 inverse.out 中。 輸出共 T 行,每行一個整數。 對於每組資料,輸出一個整數,表示答案對 998244353 取模的結果。

Sample Input

2 2 4

樣例 2 見選手目錄下的 inverse/inverse2.in 與 inverse/inverse2.ans 。

Sample Output

499122177 415935149

Data Constraint

在這裡插入圖片描述

思路

我們把這個數的位置與它的排名的位置連邊,於是他們就會構成許多環 可以發現,一個環交換次數為環的大小-1,所以我們需要儘可能多的環

問題就轉換成期望亦多少個環

可以發現只有自己連自己,環的數量才會增加,所以加入第i個點形成換的概率是1/i,所以答案就是n-sigma(1/i)(1<=i<=n)

程式碼

#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define ll long long
using namespace std;
const int N=5e4+77,M=5e4+77;
struct ahah
{
	ll L,R,p,f;
}ask[N];
struct haha
{
	ll x,y;
}ans[N];
ll answer,n,m,q,a[N],cnt[N],k;
ll gcd(ll a,ll b)
{
	return !b?a:gcd(b,a%b);
}
bool comp(ahah a,ahah b)
{
	return a.L/k==b.L/k?a.R<b.R:a.L<b.L;
}
void remove(ll pos)
{
	if(--cnt[a[pos]]>0)answer+=(-2)*(cnt[a[pos]]);}
void add(ll pos){ if(++cnt[a[pos]]>1)answer+=2*(cnt[a[pos]]-1); }
void work(ll x,ll y,ll p)
{
	if(!x)ans[p].x=0,ans[p].y=1;
	else 
	{
		ll k=gcd(x,y);
		ans[p].x=x/k;
		ans[p].y=y/k;
	}
}
int main()
{
	scanf("%lld%lld",&n,&q);
	k=sqrt(n);
	for(int i=1; i<=n; i++)scanf("%lld",&a[i]);
	for(int i=1; i<=q; i++)scanf("%lld%lld",&ask[i].L,&ask[i].R),ask[i].p=i;
	sort(ask+1,ask+1+q,comp);
	ll curl=0,curr=0;
	for(int i=1; i<=q; i++)
	{
		ll L=ask[i].L,R=ask[i].R;
		if(L==R)
		{
			ans[ask[i].p].x=0;
			ans[ask[i].p].y=1;
			continue;
		}
		while(curl<L)remove(curl++);
		while(curl>L)add(--curl);
		while(curr<R)add(++curr);
		while(curr>R)remove(curr--);
		work(answer,(R-L+1)*(R-L),ask[i].p);
	}
	for(int i=1; i<=q; i++)printf("%lld/%lld\n",ans[i].x,ans[i].y);
}