【牛客】暑假多校训练营6 B-Distance 题解

传送门:Distance
标签:组合数学

题目大意

给出两个大小都为n的多重集A和B,再给出两种操作:1、选中A中一个元素ai,令ai=ai+1;2、选中B中一个元素bi,令bi=bi+1。规定函数C(A,B)的值为将多重集A和B中的元素变得完全相同所需要的操作数,如果两个多重集无法变得完全相同,则该函数值为0。现在要你求出A中所有子集和B中所有子集间的函数值之和,答案对998244353取模。
输入:一个正整数n(1<=n<=2e3),两个大小为n的多重集A和B,其中每个元素大小不超过1e5。
输出:一个正整数代表答案。

算法分析

  • 一看数据范围就知道要用组合数,因为众所周知一个集合的非空子集数量为2n-1,如果n为2e3显然不可能跑完。我们先思考怎么简化问题。有个结论是确定的:两个大小相同的多重集一定可以通过题中的操作变得完全相同,而最优策略下的操作数就是先按升序排列,再将每个位置上的两个元素的差的绝对值相加。这个结论很容易证明,就不多赘述了。
  • 再考虑如何引入组合数。既然是答案是面向所有子集的,那么属于不同集合的任意两个元素间都必定会进行相减,我们可以将其视为一个二分图。如果枚举两个元素进行匹配,那么其对应的总贡献就是:二者之差的绝对值乘以匹配的次数。匹配的次数很好算,只要确保这两个元素在排完序的两个子集中的位置相同即可。换句话说,设这两个数分别是a,b,那么我们要让A中比a小的数和B中比b小的数相同,A中比a大的数和B中比b大的数相同,这一点可以通过组合数算出符合条件的总匹配数。
  • 这时你可能会认为需要用到容斥,因为在长度为n的两个集合中算了n次贡献。实则不然,每一位的贡献时相互独立的,并不会影响彼此,算n次恰好做到了不重不漏。最后再考虑复杂度的问题,枚举任意两个数的时间复杂度为O(n2),组合数可以通过预处理实现O(1)计算,看数据范围2000是不会T的,但我们还要枚举匹配位置的左边和右边元素的个数,这需要再开两重循环,也就是说总复杂度为O(n4)。
  • 那我们自然而然会想到通过预处理将两重循环变成一重循环,即通过O(n2)的复杂度把左边和右边的方案数两两相乘存在一个数组里,但之后不管是用枚举还是前缀和,总体n3都无法避免。题目显然限制时间复杂度最高为O(n2logn),也就是说本题的关键在于公式。根据前面的推导我们知道这个公式必然是一个单纯的组合数,且只与匹配位置左边的数或右边的数有关。假设当前枚举到的两个数为ai+1和bj+1,那么左边的方案数理论上要计算min(i,j)次,但我们对小范围数据进行打表就会发现可以用一个公式代替:Cii+j,至此本题就结束了,复杂度为O(n2)。

代码实现

#include <iostream>
using namespace std;
#include <cmath>
#include <cstring>
#include <algorithm>
int s[4005],t[4005];
const int mod=998244353;
long long fact[4005],ift[4005],inv[4005],dp[4005][4005];
long long zuhe(int m,int n){
	if(m>n)return 0;
	return fact[n]*(ift[n-m]*ift[m]%mod)%mod;
}
int main(){
	int mi,flag=1;
	long long ans=0,j1,a,b,l,r,l1,r1,c,x,x1,z,y,i,j,n,m,T,k;
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	fact[0]=ift[0]=1LL;
	inv[0]=inv[1]=1LL;
	for(i=2;i<=4000;i++){
		inv[i]=(mod-mod/i)*inv[mod%i]%mod;
	}
	for(i=1;i<=4000;i++){
		fact[i]=fact[i-1]*i%mod;
		ift[i]=ift[i-1]*inv[i]%mod;
	}
	for(i=0;i<=4000;i++)
		for(j=0;j<=4000;j++)
			if(i>j)
				dp[i][j]=dp[j][i];
			else
				dp[i][j]=zuhe(i,i+j);
	cin>>n;
	for(i=1;i<=n;i++){
		cin>>s[i];
	} 
	for(i=1;i<=n;i++){
		cin>>t[i];
	}
	sort(s+1,s+n+1);
	sort(t+1,t+n+1);
	for(i=1;i<=n;i++)
		for(j=1;j<=n;j++){
			x=abs(s[i]-t[j]);
			ans=(ans+x*dp[i-1][j-1]%mod*dp[n-i][n-j]%mod)%mod;
		}
	cout<<ans;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值