题目背景
上道题中,妖梦斩了一地的木棒,现在她想要将木棒拼起来。
题目描述
有 n 根木棒,现在从中选 4根,想要组成一个正三角形,问有几种选法?
答案对10⁹+7取模。
输入格式
第一行一个整数 n。
第二行往下 n 行,每行 1 个整数,第 i 个整数 ai代表第 i 根木棒的长度。
输出格式
一行一个整数代表答案。
输入输出样例
输入
4
1
1
2
2
输出
1
说明/提示
数据规模与约定
- 对于 30% 的数据,保证 n ≤ 5×10³。
- 对于 100%的数据,保证 1 ≤n≤ 10⁵ ,1 ≤ai≤ 5×10³。
思路解答
- 变量初始化
int Mod=1000000007;//取模数
int n=(int)streamTokenizer.nval; //n:火柴个数
int nums,count=0;//nums:ai火柴长度,count:答案
int flag[]=new int[5001];//记录某长度火柴出现个数,例flag[2]即长度为2火柴有几根
int max=-1,min=Mod;//记录火柴长度中的最大值,最小值
- 解题思路
- 四根火柴凑成一个正三角形,则必有两根火柴长度相等且等于另外两根长度之和。
- 此题数据不大,可以采取暴力枚举的方式,但暴力对象要选对。
- 【超时!】最初,我选择了暴力枚举两个火柴之和nums[i]+nums[j](即循环长度数组),并查询flag[nums[i]+nums[j]]是否>=2,是则从该个数中取两个出来(运用到排列组合C(flag[nums[i]+nums[j]],2))
nums[];//火柴长度数组
for (int i = 0; i < n-1; i++) {
for (int j = i+1; j < n; j++) {
if (nums[i]+nums[j]>nums[n-1]) break;
if (flag[nums[i]+nums[j]]>=2) {
int temp=flag[nums[i]+nums[j]];
count+=temp*(temp-1)/2;
count = count % Mod;
}
}
}
可此方法只能解决30%数据,其余时间超时。
-
【可行】随后我便转换枚举对象为枚举火柴的长度(最长10³),而不是长度数组(个数有10⁵)
-
外层循环【查找两根长度相等为i的火柴】
从长度最小遍历至长度最大,若当前火柴数大于等于2,则进入内层循环,且长度为i方案数有 C(flag[i],2) -
内层循环
要从 剩余的木棒中 取出2根长度之和为 i 的木棒。
令其中一根长度为 j,则另一根长度为 i−j。
讨论 j 与 i−j 的关系: -
若 j=i−j:即从长度为 j 的木棒中取出2根 合成一条边。
方案数为 从 flag[j]个数中取出2个数的组合,即 C(flag[j],2) -
若 j != i−j: 需从长度为 j 和 i−j 的木棒中各取出1个。
根据乘法原理,则方案数为 flag[i]*flag[i-j]
最后内外层循环方案数相乘取模即可
for (int i = min+1; i <= max; i++) {
if (flag[i]>=2){
int temp=flag[i]*(flag[i]-1)/2;
for (int j =min; j <= i/2; j++) {
if (j==i-j&&flag[j]>=2) count+=(flag[j]-1)*flag[j]*temp/2%Mod;
if (j!=i-j&&flag[j]>=1&&flag[i-j]>=1) count+=flag[j]*flag[i-j]*temp%Mod;
}
count=count%Mod;//不可少!!
}
}
代码
public class Main{
public static void main(String[] args) throws IOException {
StreamTokenizer streamTokenizer = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(System.out));
streamTokenizer.nextToken();
int Mod=1000000007;//取模数
int n=(int)streamTokenizer.nval; //n:火柴个数
int nums,count=0;//nums:ai火柴长度,count:答案
int flag[]=new int[5001];//记录某长度火柴出现个数,例flag[2]即长度为2火柴有几根
int max=-1,min=Mod;//记录火柴长度中的最大值,最小值
for (int i = 0; i < n; i++) {
streamTokenizer.nextToken();
nums=(int)streamTokenizer.nval;
flag[nums]++;
max=Math.max(max,nums);
min=Math.min(min,nums);
}
for (int i = min+1; i <= max; i++) {
if (flag[i]>=2){
int temp=flag[i]*(flag[i]-1)/2;
for (int j =min; j <= i/2; j++) {
if (j==i-j&&flag[j]>=2) count+=(flag[j]-1)*flag[j]*temp/2%Mod;
if (j!=i-j&&flag[j]>=1&&flag[i-j]>=1) count+=flag[j]*flag[i-j]*temp%Mod;
}
count=count%Mod;
}
}
printWriter.println(count);
printWriter.flush();
}