首页 > 试题广场 >

喜欢切数组的红

[编程题]喜欢切数组的红
  • 热度指数:9210 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 512M,其他语言1024M
  • 算法知识视频讲解
\hspace{15pt}小红有一个长度为 n 的数组 \{a_1,a_2,\dots,a_n\} ,她打算将数组切两刀变成三个非空子数组,使得每一个子数组中至少存在一个正数,且每个子数组的和都相等。
\hspace{15pt} 看起来不是很难,所以小红想让你求解,一共有多少种不同的切分方案。

输入描述:
\hspace{15pt}第一行输入两个整数 n \left( 3 \leqq n \leqq 2 \times 10^5 \right) 代表数组中的元素数量。
\hspace{15pt}第二行输入 n 个整数 a_1,a_2,\dots,a_n \left( -10^9 \leqq a_i \leqq 10^9 \right) 代表数组元素。


输出描述:
\hspace{15pt}在一行上输出一个整数,代表切分方案数。
示例1

输入

3
3 3 3

输出

1
示例2

输入

6
1 1 4 5 1 4

输出

0
示例3

输入

10
0 3 4 2 3 2 1 -1 3 4

输出

2
import java.util.*;

/*

    暴力穷举
   
    两个条件:
    1.子数组至少存在一个正数
    2.每个子数组和都相等

    先使用前缀和找出所有的和相等的情况,然后在该情况对是否所有子数组至少存在一个正数做判断

*/


public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int n = sc.nextInt();
        int[] nums = new int[n];
        //前缀和,代表当前索引即之前的所有元素的和
        int[] pre = new int[n];
        //统计正数个数
        int[] pos = new int[n];
        for (int i = 0; i < n; i++) {
            nums[i] = sc.nextInt();
        }
        pre[0] = nums[0];
        if (nums[0] > 0) {
            pos[0] = 1;

        } else {
            pos[0] = 0;
        }
        for (int i = 1; i < n; i++) {
            pre[i] = nums[i] + pre[i - 1];
            if (nums[i] > 0) {
                pos[i] = pos[i - 1] + 1;
            } else {
                pos[i] = pos[i - 1];
            }
        }
        // for (int i = 0; i < n; i++) {
        //     // pre[i] = nums[i] + pre[i - 1];
        //     System.out.println(pre[i]);
        // }

        //控制两个下标检索
        //计算每个子数组累计和

        /*
            假设i是第一刀,j是第二刀
            sum1=pre[i]
            sum2=pre[j]-pre[i];
            sum3=pre[n-1]-pre[j];
            避免遍历

            正数判断复杂度太高
            剪枝
            先判断正数

        */
        int count = 0;
        // boolean first = false;
        //第一刀从1-倒数第三个
        for (int i = 0; i <= n - 3; i++) {

            // if (nums[i] > 0) first = true;
            if (pos[i] <= 0) continue;
            int sum1 = pre[i];
            if (sum1 != pre[n - 1] / 3) continue;
            // boolean second = false;
            //第二刀从i+1-倒数第二个
            for (int j = i + 1; j <= n - 2; j++) {

                // if (nums[j] > 0) second = true;
                if (pos[j] - pos[i] <= 0) continue;
                if (pos[n - 1] - pos[j] <= 0) continue;

                int sum2 = pre[j] - pre[i];
                // if (sum1 != sum2) continue;
                int sum3 = pre[n - 1] - pre[j];

                //满足条件2
                if (sum1 == sum2 && sum2 == sum3) {

                    count++;

                }
            }

        }

        System.out.println(count);

    }

}
n^2的暴力求解+剪枝
本来是前缀和求子数组值,先判断子数组和都相等,再判断包含正数,超时;
后面先判断包含正数再判断和相等,超时;
思考了一下包含正数也可以用前缀和(本来用遍历),超时;
发现在外层循环就可以获取到第一部分前缀和,通过判断该前缀和是否为原数组值三分之一(本来都没想到,看到讨论里有个大佬在说这个)剪枝,通过
发表于 2025-04-13 19:10:33 回复(0)
从左往右找一刀,从右往左找一刀。
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextInt()) { // 注意 while 处理多个 case
            int n = Integer.parseInt(in.nextLine());
            int sum = 0;
            int[] arr = new int[n];
            for(int i = 0; i < n; i++){
                arr[i] = in.nextInt();
                sum += arr[i];
            }
            if(sum % 3 != 0){
                System.out.println(0);
                continue;
            }
            int avg3 = sum / 3;
            int sum1 = 0;//第一个子数组和
            int count = 0;
            int rightIndex = n - 1;
            int firstflag = 0;//第一个数组是否有正数
            int secondflag = 0;//第三个数组是否有正数
            for(int i = 0; i < rightIndex - 3; i++){
                firstflag = Math.max(firstflag,arr[i]); 
                sum1 += arr[i];
                //从左往右找到第一刀
                if(sum1 == avg3 && firstflag > 0){
                    int sum2 = 0;//第三个子数组和
                    //从右往左找到第二刀
                    secondflag = 0;
                    for(int j = rightIndex; j >= i+2; j--){
                        secondflag = Math.max(secondflag,arr[j]);
                        sum2 += arr[j];
                        if(sum2 == avg3 & secondflag > 0 && hasZhengShu(arr,i + 1,j)){
                            count++;
                        }
                    }
                }
            }
            System.out.println(count);
        }
    }

    //判断数组内是否有正数
    static boolean hasZhengShu(int[] arr, int i, int j){
        for(; i < j; i++){
            if(arr[i] > 0){
                return true;
            }
        }
        return false;
    }
}


发表于 2025-04-08 01:45:19 回复(0)
//隔壁兄弟代码的复杂度是O(n*n) 我的代码复杂度是O(n log n) 即遍历所有jlist的值 但是ilist的每个值只执行一次  大大缩减 俩种都是对的 只是用例数据量太少了 当数据量超过2e5次方后(2乘以10的5次方) 复杂度是O(n*n)会超时  PS:D老师的线段树实在是看不懂 但是逻辑是一样的  大体思路是写3个数组 分别判断第一段 第二段 第三段数据有正数   先筛选符合条件的i j 分别组成ilist 和 jlist列表存放 条件是num[i]=总和/3 num[j]=总和*2/3  并且对应的判断数组要true
再就是遍历和组合不同的i j 找出符合的(i,j)对 计算数量 当j第一个元素a满足了ilist列表中的部分 j取第二个元素的时候 之前a满足的 b就不用在判断了 直接通过 即数量+1 判断比a大但是比b小的i元素即可 (PS 这里有个坑 a之前可以没有正数,但【a,b】区间的可能有正数 这些数量要加上) 最后计算总数量 out


import java.util.Scanner;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int[] s = new int[n + 1]; //原始数据
        long[] num = new long[n + 1];
        long tol = 0;
        for (int i = 1; i < n + 1; i++) {
            s[i] = in.nextInt();
            tol = tol + s[i];
            num[i] = num[i - 1] + s[i];
        }

        if (tol % 3 != 0) {
            System.out.println(0);
            return;
        }
        long p = tol / 3;
        //构造第一组数据有正数的判断
        boolean[] first = new boolean[n + 1]; //第一段
        first[0] = false;
        for (int i = 1; i < n + 1; i++) {
            first[i] = first[i - 1] || (s[i] > 0);
        }
        int[] second = new int[n + 1];
        second[n] = n + 1;
        for (int i = n - 1; i >= 0; i--) {
            if (s[i] > 0) {
                second[i] = i;
            } else {
                second[i] = second[i + 1];
            }
        }
        boolean[] third = new boolean[n + 1]; //记录每个位置之后是否存在正数
        third[n] = false;
        for (int i = n - 1; i >= 0; i--) {
            third[i] = (s[i + 1] > 0) || third[i + 1];
        }
        List<Integerilist = new ArrayList<>();
        for (int i = 1; i < n + 1; i++) {
            if (num[i] == p && first[i]) {
                ilist.add(i);
            }
        }
        List<Integerjlist = new ArrayList<>();
        for (int i = 1; i < n + 1; i++) {
            if (num[i] == 2 * p && third[i]) {
                jlist.add(i);
            }
        }
        Collections.sort(ilist);
        Collections.sort(jlist);
        int z = 0;//ilist里的元素序号
        List<Integertest = new ArrayList<>();
        int temp = 0;
        int previoustemp = 0;
        int preresult = 0;
        for (int j : jlist) { //满足条件i<j 并且second[i]<j 第二段要有正数
            int result = 0;
            if(previoustemp<=j){
                result =z;
            }else{
                result = preresult;
            }
            while (z < ilist.size() && ilist.get(z) < j) {
                temp = second[ilist.get(z)];
                if (temp <= j) {
                    result++;
                }
                z++;
            }
            previoustemp = temp;
            preresult = result;
            test.add(result);
        }
        int ans = 0;
        for (int k = 0; k < test.size(); k++) {
            ans = ans + test.get(k);
        }
        System.out.println(ans);
    }
}
发表于 2025-03-30 15:17:11 回复(0)
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int a = in.nextInt();
        //原始数组
        int[] num = new int[a];
        //记录正整数的个数,累计的
        int[] zs = new int[a];
        //记录每个索引位置求和
        int[] sum = new int[a];
        int z=0;
        int s=0;
        for(int i=0;i<a;i++){
            int b = in.nextInt();
            num[i]=b;
            if(b>0){
                z++;
            }
            zs[i]=z;
            s+=b;  
            sum[i]=s;
        }
        int count=0;
        int target = sum[a-1]/3;
        for(int i=0;i<a-2;i++){
            if(zs[i]==0 || sum[i]!=target){
                continue;
            }
            if(zs[a-1]-zs[i]==0){
                    break;
            }
            for(int j=i+1;j<a-1;j++){
                if(zs[j]-zs[i]==0){
                    continue;
                }
                if(zs[a-1]-zs[j]==0){
                    break;
                }
                if(sum[j]-sum[i]==target){
                        count++;
                }

            }
        }
        System.out.print(count);
    }
}
发表于 2025-03-28 17:43:56 回复(0)