题目
我们考虑从(1)到(n)把每个数放到序列里面去,以消掉绝对值。
在最后的序列中,如果(i)的某一边是序列的边界,那么(i)会产生(0)的贡献。如果(i)的某一边是一个比(i)小的数,那么(i)会产生(i)的贡献。如果(i)的某一边是一个比(i)大的数,那么(i)会产生(-i)的贡献。
当我们放(i)时,所有小于(i)的数都已经放进序列了,而大于(i)的还没放。
那么最后的序列中(i)的左右两边的情况就等同于放(i)的时候(i)两边是否紧贴着某个数。
所以我们可以把连通块(一个极大的连续的放了数的段)的情况和边界的情况放进dp的状态里。
设(f_{i,j,k,l})表示放了(i)个数,序列中存在(j)个连通块,总贡献为(k)(这一维需要平移(5000)),有(l)个边界已经被紧贴((l=0,1,2))的方案数。
发现第一维(i)显然是可以滚掉的。
那么有以下几个转移:
(1.i)一边是边界,一边是空:(f_{j,k,l}(2-p)->f_{j+1,k-i,l+1})。
(2.i)一边是边界,一边是连通块:(f_{j,k,l}(2-p)->f_{j,k+i,l+1})。
(3.)两边都是连通块:(f_{j,k,l}(j-1)->f_{j-1,k+2i,l})。
(4.)两边都是空:(f_{j,k,l}(j+1-l)->f_{j+1,k-2i,l})。
(5.)一边是连通块,一边是空:(f_{j,k,l}(j*2-l)->f_{j,k,l})。
那么最后的答案就是(frac{sumlimits_{i=5000+m}^{10000}f_{1,i,2}}{n!})。
注意要数据类型分治。
#include<bits/stdc++.h>
using namespace std;
const int N=107,M=10007;
namespace db{long double f[2][N][M][3];}
namespace flt{__float128 f[2][N][M][3];}
int min(int a,int b){return a<b? a:b;}
int n,m,k;
template<class T>void out(T ans)
{
cout<<"0.",ans*=10;
for(int i=1;i<=k;++i) cout<<(int)(ans+(k==i)*0.5),ans=(ans-(int)ans)*10;
}
template<class T>void solve(T f[][N][M][3])
{
T ans=0;
f[0][0][5000][0]=1;
int g=0,i,j,k,p;
for(i=1;i<=n;++i)
{
g^=1,memset(f[g],0,sizeof f[g]);
for(j=0;j<=min(i-1,m);++j)
for(k=0;k<=10000;++k)
for(p=0;p<=2;++p)
if(f[g^1][j][k][p])
{
if(k>=2*i) f[g][j+1][k-2*i][p]+=f[g^1][j][k][p]*(j+1-p);
if(j) f[g][j][k][p]+=f[g^1][j][k][p]*(j*2-p);
if(j>=2&&k+2*i<=10000) f[g][j-1][k+2*i][p]+=f[g^1][j][k][p]*(j-1);
if(p^2)
{
if(k>=i) f[g][j+1][k-i][p+1]+=f[g^1][j][k][p]*(2-p);
if(j&&k+i<=10000) f[g][j][k+i][p+1]+=f[g^1][j][k][p]*(2-p);
}
}
}
for(i=m;i<=5000;++i) ans+=f[g][1][5000+i][2];
for(i=1;i<=n;++i) ans/=i;
out(ans);
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
if(k<=8) solve(db::f);
else solve(flt::f);
}