P3799. 妖梦拼木棒

考点

  • 枚举
  • 排列组合

题解

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
54
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

int arr[5050];

const int mod = 1e9 + 7;

//求组合数,限定取2或1
ll calc(ll a, int b)
{
if (b == 2)
return ((a % mod) * ((a - 1) % mod) / 2) % mod;
else
return a % mod;
}

int main()
{
int n, in;
ll ans = 0;
cin >> n;
while (n--)
{
cin >> in;
++arr[in];
}
for (int i = 5000; i >= 1; i--)
{
if (arr[i] < 2)
continue;
ll a = calc(arr[i], 2);
for (int j = 1; j <= i / 2; ++j)
{
if (!arr[j] || !arr[i - j])
continue;
//两个短边相等的时候
if (j == i - j && arr[j] >= 2)
{
ll b = calc(arr[j], 2);
ans = (ans + (a * b) % mod) % mod;
}
if (j != i - j)
{
ll b = calc(arr[j], 1), c = calc(arr[i - j], 1);
ans = (ans + (a * b * c) % mod) % mod;
}
}
}
cout << ans;
return 0;
}

思路

用一个数组arr记录各类长度的个数;如果双重循环枚举短边的话,是绝对会超时的

由于其中一条短边一定小于等于长边的一半;那么外循环就枚举长边,内循环枚举小于等于长边一半的短边,复杂度就降到了\(n^2/2\)

假设长边为a,一个短边为b,另一个短边为c,有以下等式成立: \[ \frac{a}{2}=\frac{\left( b+c \right)}{2} \] 映射到数轴上看,显然有一个短边必小于等于长边一半,而另一个短边必大于等于长边一半

找到了符合条件的三种边后,使用排列乘法原理即可:

  • 如果短边b等于短边c,相当于从长度等于b的火柴中取2根,再从长度等于a长边的火柴中取2根,即: \[ C_{a}^{2}\times C_{b}^{2} \]

  • 如果短边b不等于短边c,分别从长度b、长度c两堆各取1根,从长度a取2根 \[ C_{a}^{2}\times C_{b}^{1}\times C_{c}^{1} \]