牛客挑战赛 32 :D 放物品(错排问题 + 分类讨论)

在这里插入图片描述


直接枚举每一对点,计算贡献以及有效贡献次数。
会发现,贡献是一个等差数列的求和形式,可以预处理出来然后求和。
贡献次数这里要分类讨论,设当前处理点对是 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值