P4104 [HEOI2014] 平衡

P4104 [HEOI2014] 平衡

题面

题目描述:
一个杠杆上的左右两侧各有 \(n\) 块质量相等的橡皮(杠杆最中间也有一块),随机从中拿走 \(k\) 块橡皮,可使杠杆依然平衡。求共有多少种拿走橡皮的方案数,答案对 \(p\) 取模。

题目简译:
\(-n\sim n\) 中共选出 \(k\) 个互不相同的数,求使得正数与负数的和为零的方案数。

思路

前置知识:整数划分

只考虑在杠杆的右侧选出 \(x\in [1,k]\) 个数,题目就变成了经典的整数划分模型:
\(N\in [0,n\times k]\) 划分为 \(k\) 个互不相同且不大于 \(n\) 的正整数的方案数。
显然统计答案时,右侧选出 \(x\) 个数,那么左侧就选出 \(k-x\) 个数,乘法原理计算得到总方案数。


对于数字有大小限制的整数划分:

\(dp_{i,j}\) 表示把 \(i\) 划分为 \(j\) 个数的方案:
则有 \(dp_{i,j} = dp_{i-j,j}+dp_{i-j,j-1}\)
\(i>n\) 时,出现的不合法方案需要减掉:
此时 \(dp_{i,j} = dp_{i-j,j}+dp_{i-j,j-1}-dp_{i-n-1,j-1}\)

对于统计答案:

如上:枚举杠杆一侧选出 \(x\) 个数,另一侧就选出 \(k-x\) 个数,乘法原理计算。
特别注意:杠杆最中间也有一块橡皮,需要考虑是否将其拿走。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<climits>
#include<cstdlib>
#include<vector>
using namespace std;
typedef long long ll;
template <typename T>
inline void read(T&x){//快读
	int w=0;x=0;
	char ch = getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') w=1;
		ch = getchar();
	}
	while(ch>='0'&&ch<='9'){
		x = (x<<1)+(x<<3)+(ch^48);
		ch = getchar();
	}
	if(w) x=-x;
}
template <typename T,typename...Args>
inline void read(T&t,Args&...args){
	read(t);read(args...);
}
const int N = 1e4+10,K = 15;
int n,k,p;
ll dp[N*K][K];
int main(){
	int T;read(T);
	while(T--){
		read(n,k,p);
		memset(dp,0,sizeof(dp));
		dp[0][0] = 1;//初始状态
		for(int i=0;i<=k*n;++i){//整数划分
			for(int j=1;j<=min(i,k);++j){
				dp[i][j] = (dp[i][j]+dp[i-j][j]+dp[i-j][j-1])%p;
				if(i>n) dp[i][j] = (dp[i][j]-dp[i-n-1][j-1]+p)%p;
			}
		}
		ll ans = 0;
		for(int j=0;j<=k;++j){
			for(int i=0;i<=n*k;++i){
				ans = (ans+dp[i][j]*dp[i][k-j])%p;
				if(j<k) ans = (ans+dp[i][j]*dp[i][k-j-1])%p;//最中间的橡皮要看是否选
			}
		}
		printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2025-03-23 10:58  Tmbcan  阅读(51)  评论(0)    收藏  举报