2026.2.24 模拟赛

https://oj.gxyzh.com/d/hzoj/contest/6966f780c01e1c1c7ca5b72f

差值

题解

不妨先排序。
易于得到一个数组的差值和为 \(2\sum_{i=1}^n(a_i\times i)-(n+1)\sum_{i=1}^n a_i\)

对于 \(n\le 10\),使用状压暴力枚举。

对于 \(n\le 2000\),考虑数组中每一个数对上面的式子的贡献。
将贡献拆成拆成两部分:\(\sum_{i=1}^n(a_i\times i)\) 与 \(\sum_{i=1}^n a_i\)。
第一个:对于每个数枚举,钦定它的排名贡献为 \(2\sum_{i=1}^n\sum_{j=1}^ka_i\times j\times C_{i-1}^{j-1}\times C_{n-i}^{k-j}\).
第二个:直接算出每个数的计算次数 \((k+1)C_{n-1}^{k-1}\sum_{i=1}^na_i\)(钦定这个数必出现,剩下的再选)。
时间复杂度为 \(O(n^2)\)(然后我不会优化)

对于 \(n\le 20000\),直接考虑差值的贡献。
发现: \((a_i,a_j)\) 的差值产生贡献,等价于 \(a_i,a_j\) 都被选进数组。
那么每个差值产生的贡献就是 \(C_{n-2}^{k-2}\)。
所以 \(ans=C_{n-2}^{k-2}\times (2\sum_{i=1}^n(a_i\times i)-(n+1)\sum_{i=1}^n a_i)\)
时间复杂度为 \(O(nlogn)\)。

代码

#include<iostream>
#include<algorithm> 
#define  int  long long
using namespace std;
constexpr int N=2e5+5,p=1e9+7;
int n,k,a[N],b[N],ans;
int fac[N],inv[N],fac_inv[N];
void init(){
	fac[0]=inv[0]=fac_inv[0]=1;
	fac[1]=inv[1]=fac_inv[1]=1;
	for(int i=2;i<=n;i++)
		fac[i]=fac[i-1]*i%p,
		inv[i]=(p-p/i)*inv[p%i]%p,
		fac_inv[i]=fac_inv[i-1]*inv[i]%p;
}int C(int n,int m){
	if(n<m)return 0;
	return fac[n]*fac_inv[m]%p*fac_inv[n-m]%p;
}signed main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>k,init();
	for(int i=1;i<=n;i++)cin>>a[i];
	sort(a+1,a+n+1);
	for(int i=1;i<=n;i++)
		ans=(ans+2*a[i]*i%p-(n+1)*a[i]%p)%p;
	ans=ans*C(n-2,k-2)%p;
	cout<<(ans+p)%p;
	return 0;
}

跳槽

题解

对于 \(n\le 10\),爆搜即可(\(10\) 以内跑得飞快)。

对于 \(n\le 100\),转化题意;
注意到爆搜检验时用到了单调栈,再仔细思考并画图,
不难发现跳槽就相当于沿着笛卡尔树的边向上!
而 \(l[i]\) 和 \(r[i]\) 就是左右子树中最长链长度!
那么 \(|l[i]-r[i]|\le 1\) 不就是规定了一棵 \(AVL\) 树?
于是题目就是求:节点数为 \(n\) 的笛卡尔树并且还是 \(AVL\) 树的情况。
笛卡尔树的题经常考虑区间 \(dp\)。
在此题中设 \(dp_{i,j}\) 表示节点数为 \(i\),深度为 \(j\) 的情况数。
易于有 \(dp_{i,j}=\sum_{l=0}^{i-1}(dp_{l,j-1}\times dp_{i-1-l,j-1}+dp_{l,j-2}\times dp_{i-1-l,j-1}+dp_{l,j-1}\times dp_{i-1-l,j-2})\times C_{i-1}^l\)
时间复杂度为 \(O(n^3)\)。

对于 \(n\le 5000\),考虑剪枝。
注意到 \(AVL\) 树其实是一棵平衡树……
所以深度从 \(logi+1\) 开始枚举,若 \(dp_{i,j}=0\) 就跳出。
经实测,在 \(n\le 5000\) 的范围内,只需要枚举 \(4\) 次 \(j\)。
时间复杂度为 \(O(n^2)\)。

附加:虽然题目至多考虑到 \(O(n^2)\),但是秉持着抢最优解的原则,我们继续优化。
发现,最内层循环还可以进行剪枝(寻找上下界),发现实际上只需枚举 \(O(logn)\) 次。
时间复杂度为 \(O(nlogn)\)。

代码

#include<iostream>
#define  int  long long
using namespace std;
constexpr int N=5e3+5,p=1e9+7;
int n,dp[N][N],ans,log[N];
int fac[N],inv[N],fac_inv[N];
void init(){
	fac[0]=inv[0]=fac_inv[0]=1;
	fac[1]=inv[1]=fac_inv[1]=1;
	for(int i=2;i<=n;i++)
		fac[i]=fac[i-1]*i%p,
		inv[i]=(p-p/i)*inv[p%i]%p,
		fac_inv[i]=fac_inv[i-1]*inv[i]%p;
	log[0]=-1;
	for(int i=1;i<=n;i++)log[i]=log[i>>1]+1;
}int C(int n,int m){
	return fac[n]*fac_inv[m]%p*fac_inv[n-m]%p;
}signed main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n,dp[1][1]=1,dp[0][0]=1,init();
	for(int i=2;i<=n;i++){
		for(int dep=log[i]+1;dep<=n;dep++){
			for(int L=0,R=i-1;R>=0;L++,R--){
				int cnt=0;
				cnt+=dp[L][dep-1]*dp[R][dep-1]%p;
				cnt+=dp[L][dep-2]*dp[R][dep-1]%p;
				cnt+=dp[L][dep-1]*dp[R][dep-2]%p;
				dp[i][dep]=(dp[i][dep]+cnt*C(i-1,L)%p)%p;
			}if(!dp[i][dep])break;
		}
	}for(int i=1;i<=n;i++)ans=(ans+dp[n][i])%p;
	cout<<ans<<'\n';
	return 0;
}
posted @ 2026-02-24 15:34  Zhou_Lemon  阅读(20)  评论(0)    收藏  举报