P4104 [HEOI2014] 平衡

题面

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

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

思路

前置知识:整数划分

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


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

表示把 划分为 个数的方案:
则有
时,出现的不合法方案需要减掉:
此时

对于统计答案:

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

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#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;
}