直接枚举每一对点,计算贡献以及有效贡献次数。
会发现,贡献是一个等差数列的求和形式,可以预处理出来然后求和。
贡献次数这里要分类讨论,设当前处理点对是 i,j
1:i 占了 j 的位置,j也占了 i 的位置。贡献次数:d[n][0]
2:i 占了 j 的位置,或 j 占了 i 的位置。贡献次数:d[n][1]
3:i 不占 j 的位置,j 也不占 i 的位置。贡献次数:d[n][2]
d[n][i] 表示 n 个数,其中有 i 个数 是一定错排的情况下(因为他们的位置被当前枚举的点对占了),错排的数量
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int maxn = 3e3 + 10;
int n,t,v[maxn];
ll pw[maxn],sum[maxn],d[maxn][3];
ll fpow(ll a,ll b) {
ll r = 1;
while(b) {
if(b & 1) r = r * a % mod;
b >>= 1;
a = a * a % mod;
}
return r;
}
int main() {
scanf("%d",&t);
for (int i = 1; i <= 2000; i++) {
pw[i] = 1ll * i * (i - 1) / 2 % mod;
sum[i] = (sum[i - 1] + pw[i]) % mod;
}
//三类错排,第二维表示n个数里有几个数是一定没有自己位置的
d[0][0] = 1;d[2][0] = 1;
for (int i = 3; i <= 2000; i++)
d[i][0] = 1ll * (i - 1) * (d[i - 1][0] + d[i - 2][0]) % mod;
d[1][1] = 1;
d[2][1] = 1;
d[2][2] = 2;
for (int i = 3; i <= 2000; i++) {
d[i][1] = (d[i][0] + d[i - 1][0]) % mod;
d[i][2] = (2 * d[i - 2][0] + 4 * d[i - 1][0] % mod + (i - 2) * (i - 3) % mod * d[i - 2][2] % mod) % mod;
}
while(t--) {
scanf("%d",&n);
for (int i = 1; i <= n; i++)
scanf("%d",&v[i]);
ll ans = 0,tmp = 0;
for (int i = 1; i <= n; i++) {
ans = 0;
for (int j = i + 1; j <= n; j++) {
ll p = j - i;
if (v[i] < v[j]) ans = (ans + (v[j] - v[i]) * p % mod * d[n - 2][0] % mod) % mod;
//第一类,互相占了对方的位置
ll z = sum[n];
ll r = pw[v[j]] + pw[n - v[i] + 1],k = pw[v[i]] + pw[n - v[j] + 1];
// r 表示 i占了 j 或者 j 占了 i的贡献,要扣掉两种都占的情况
// k 表示 i 占了 i 或者 j 占了 j 的贡献,也要扣掉两种都占的情况
if (v[i] < v[j]) r = (r - 2 * (v[j] - v[i] + mod) % mod + mod) % mod;
if (v[i] > v[j]) k = (k - 2 * (v[i] - v[j] + mod) % mod + mod) % mod;
ans = (ans + r * d[n - 2][1] % mod * p % mod) % mod;
//第二类,i 占了 j 或者 j 占了 i
z = z - r - k - abs(v[i] - v[j]);
z = (z % mod + mod) % mod;
ans = (ans + z * p % mod * d[n - 2][2] % mod) % mod;
//第三类 ,i 不占 j ,j 也不占 i,z 扣掉不合法的情况以及 前两类剩下的就是第三类的贡献
}
tmp = (tmp + ans) % mod;
}
printf("%lld\n",tmp);
}
return 0;
}