洛谷 P3799 妖梦拼木棒 JAVA详解

题目背景

上道题中,妖梦斩了一地的木棒,现在她想要将木棒拼起来。

题目描述

有 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³。

思路解答

  1. 变量初始化
        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;//记录火柴长度中的最大值,最小值
  1. 解题思路
  • 四根火柴凑成一个正三角形,则必有两根火柴长度相等且等于另外两根长度之和。
  • 此题数据不大,可以采取暴力枚举的方式,但暴力对象要选对。
  • 【超时!】最初,我选择了暴力枚举两个火柴之和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();

    }
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值