花掉了自己用来搞学科的时间做了这道题……
一道类似的题:Here
考虑拆开绝对值计算贡献。那么我们对于(1)到(N)的排列,从小到大地将插入它们插入排列中。
假设我们现在计算到了数(i),这意味着前(i-1)个数已经被插入到了排列中。考虑当前如何计算(i)的贡献。
不难发现:在最终的排列中,(i)的贡献与它和前(i-1)个数和边界的相邻情况有关。如果(i)某一边与边界相邻,会产生(0)的贡献;某一边与小于(i)的数相邻,会产生(i)的贡献;某一边与大于(i)的数相邻,会产生(-i)的贡献。
但是在这里大于(i)的数还没有被插入,所以这里必须要强制数(i)与前(i-1)个数和边界的相邻情况才能够在当前阶段计算出(i)对序列价值的贡献。
故设(f_{i,j,k,l})表示放完了前(i)个数、数列中存在(j)个连通块(定义连通块为一段极长区间,满足这一段区间任意的相邻的两个数都被强制定为相邻,也就是说在之后的转移中,这一段区间内不能插入数)、数列总价值为(k-5000)、有(l)个边界已经与某个数强制相邻的方案数。
转移:
(a.)一边与边界相邻,一边不与当前产生的连通块相邻,产生(-i)的价值,方案数为(2-l)
(b.)一边与边界相邻,一边与当前产生的连通块相邻,产生(i)的价值,方案数为(2-l),要求(j eq 0)
(c.)两边均与当前产生的连通块相邻,产生(2i)的价值,方案数为(j-1),要求(j geq 2)
(d.)两边均不与当前产生的连通块相邻,产生(-2i)的价值,方案数为(j+1-l)
(e.)一边与当前产生的连通块相邻,另一边不与当前产生的连通块相邻,产生(0)的价值,方案数为(j*2-l),要求(j eq 0)
注意到(de)两种转移方案数都减去了(l),因为对于两端都不与边界相邻的连通块,可以选择左右之一与当前的数相邻,但是有一段与边界相邻的连通块只有一端可以。
最后的答案就是(frac{sumlimits_{i=5000+M}^{10000} f_{N,1,i,2}}{n!})
然后这鬼题还要数据类型分治……(K leq 8)使用long double,(K leq 30)使用__float128
// luogu-judger-enable-o2
#include<bits/stdc++.h>
//This code is written by Itst
using namespace std;
inline int read(){
int a = 0;
char c = getchar();
bool f = 0;
while(!isdigit(c)){
if(c == '-')
f = 1;
c = getchar();
}
while(isdigit(c)){
a = (a << 3) + (a << 1) + (c ^ '0');
c = getchar();
}
return f ? -a : a;
}
namespace db{long double dp[2][110][10010][3];}
namespace flt{__float128 dp[2][110][10010][3];}
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 >
inline void solve(T dp[][110][10010][3]){
T ans = 0;
dp[0][0][5000][0] = 1;
int now = 0;
for(int i = 1 ; i <= N ; ++i){
now ^= 1;
memset(dp[now] , 0 , sizeof(dp[now]));
for(int j = 0 ; j <= min(i - 1 , M) ; ++j)
for(int k = 0 ; k <= 10000 ; ++k)
for(int p = 0 ; p <= 2 ; ++p)
if(dp[now ^ 1][j][k][p]){
if(k - 2 * i >= 0)
dp[now][j + 1][k - 2 * i][p] += dp[now ^ 1][j][k][p] * (j + 1 - p);
if(j)
dp[now][j][k][p] += dp[now ^ 1][j][k][p] * (j * 2 - p);
if(j >= 2 && k + 2 * i <= 10000)
dp[now][j - 1][k + 2 * i][p] += dp[now ^ 1][j][k][p] * (j - 1);
if(p != 2){
if(k - i >= 0)
dp[now][j + 1][k - i][p + 1] += dp[now ^ 1][j][k][p] * (2 - p);
if(j && k + i <= 10000)
dp[now][j][k + i][p + 1] += dp[now ^ 1][j][k][p] * (2 - p);
}
}
}
for(int i = M ; i <= 5000 ; ++i)
ans += dp[now][1][5000 + i][2];
for(int i = 1 ; i <= N ; ++i)
ans /= i;
out(ans);
}
int main(){
#ifndef ONLINE_JUDGE
//freopen("in" , "r" , stdin);
//freopen("out" , "w" , stdout);
#endif
N = read();
M = read();
K = read();
if(K <= 8)
solve(db::dp);
else
solve(flt::dp);
return 0;
}