题解:P16044 [ICPC 2022 NAC] Double Sort
lailai0916 · · 题解
题意简述
从
解题思路
设第一次排序后的数为
第二式适用于
所有
将差分排序为
固定一个正整数高度
排序后共有
其中
接下来求
现在只需由二项矩恢复截断函数。令
二项式反演给出:
交换
当
当
将
若
若
式中存在巨大正负项相消,直接用浮点数累加会丢失精度。代码使用 cpp_int 精确计算组合数、正项和与负项和,完成相减后才转为 long double 做一次除法。
组合数
正确性证明
差分序列中的每一项均为正数,其前缀和严格递增且不超过
对固定高度
计算
最后交换的都是有限求和,正负项由大整数精确保存。故
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
using ull=unsigned long long;
using u128=unsigned __int128;
using ld=long double;
const int N=55;
const int M=10005;
const int L=20;
const ull base=1000000000;
struct Big
{
ull a[L];
Big(ull x=0)
{
memset(a,0,sizeof a);
a[0]=x;
}
Big &operator+=(const Big &x)
{
ull cur=0;
for(int i=0;i<L;i++)
{
cur+=a[i]+x.a[i];
a[i]=cur%base;
cur/=base;
}
return *this;
}
Big &operator-=(const Big &x)
{
int cur=0;
for(int i=0;i<L;i++)
{
ll val=(ll)a[i]-x.a[i]-cur;
if(val<0)
{
val+=base;
cur=1;
}
else cur=0;
a[i]=val;
}
return *this;
}
void mul(ull x)
{
u128 cur=0;
for(int i=0;i<L;i++)
{
cur+=(u128)a[i]*x;
a[i]=cur%base;
cur/=base;
}
}
void div(ull x)
{
ull cur=0;
for(int i=L-1;i>=0;i--)
{
cur=cur*base+a[i];
a[i]=cur/x;
cur%=x;
}
}
ld val()const
{
ld res=0;
for(int i=L-1;i>=0;i--)res=res*base+a[i];
return res;
}
};
ull c[N][N];
Big comb[M],sum[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,m;
cin>>n>>m;
for(int i=0;i<=n;i++)
{
c[i][0]=c[i][i]=1;
for(int j=1;j<i;j++)c[i][j]=c[i-1][j-1]+c[i-1][j];
}
comb[n]=Big(1);
for(int i=n+1;i<=m;i++)
{
comb[i]=comb[i-1];
comb[i].mul(i);
comb[i].div(i-n);
}
for(int i=1;i<=n;i++)
{
for(int j=m;j>=n;j-=i)sum[i]+=comb[j];
}
cout<<fixed<<setprecision(10);
for(int i=1;i<=n;i++)
{
int k=n-i;
Big pos,neg;
if(!k)
{
pos=sum[1];
pos.mul(n);
}
else
{
for(int j=k+1;j<=n;j++)
{
Big cur=sum[j];
cur.mul(c[j-2][k-1]);
cur.mul(c[n][j]);
if((j-k-1)&1)neg+=cur;
else pos+=cur;
}
}
pos-=neg;
cout<<pos.val()/comb[m].val()<<'\n';
}
return 0;
}