2023年05月31日-暑期实习-第三题(300分)-太阳能发电板

在线评测链接

题目内容

塔子哥正在承包月亮市星星区的一片农场。为了提高农场的利润,塔子哥打算选取部分区域引进高级培育技术。已知进行试点的区域的农作物价值计算公式如下:最大利润 = 选中区域中的各地块收入总和 - 区域内地块个数 × \times × 5 5 5
整个农场区域是一个矩形区域,被划分为 n × m n \times m n×m 个地块。在进行规划前,农场技术人员对每个地块进行了勘测,计算出每个地块的最大年产量,使用 n × m n \times m n×m 矩阵表示。

为了最大化农场的利润,并降低管理成本,塔子哥打算选择一个矩形区域引进高级培育技术。他想知道,应该选择多少个地块进行引进高级培育技术,以获得最大的年利润。

请实现一个功能,求出应该引进高级培育技术的地块数量,以及能够获得的最大年利润。

输入描述

输入第一行为两个正整数: n n n m m m, 使用空格分隔。其中 A A A 地区分成为 n n n × \times × m m m 个地块: 1 1 1 ≤ \leq m , n m,n m,n ≤ \leq 100 100 100,

接下来 n n n行 ,每行 m m m 个正整数。表示每小块区域面积的最大利润收入。 0 0 0 ≤ \leq 收入 ≤ \leq 100 100 100.

输出描述

输出一行为两个整数值,使用空格分隔。第一个值表示引进高级培育技术的地块数,第二个值表示最大利润。

注意:如果存在最大利润相同的情况,则输出引进高级培育技术较小的结果

如果所有地块利润都是负数,则需选择损失最少的那个地块,引进高级培育技术。

样例1

输入

3 3
2 6 9
9 4 9
8 7 3

输出

9 12

解释

全部选择

样例2

输入

3 3
2 6 1
1 4 1
8 7 3

输出

1 3

解释

可以证明,选择左下角那个8是最优解。面积是1 * 1 = 1 , 大小是 8 - 5 = 3

思路:二维前缀和

给定一个 n ∗ m n*m nm 的矩阵,矩阵中的每个点,都有一个价值 a [ i ] [ j ] a[i][j] a[i][j] ,我们需要找到一个价值最大的矩阵,该矩阵的价值为其范围内所有点的价值之和,同时,每包含一个点,就需要减去 5 5 5 的价值。

我们很容易想到,在直到矩阵大小后,我们能够 O ( 1 ) O(1) O(1) 的计算出里面有多少个点,但是我们缺少一种方法,一种能够快速求得矩阵内价值总和的方法。

这种方法就是二维前缀和,它能够在 O ( N M ) O(NM) O(NM) 的预处理后, O ( 1 ) O(1) O(1) 获得我们想要的答案。

二位前缀和的原理如下:

假设我们有一个矩阵 a a a

定义:
$$
a = \begin{Bmatrix}
0&1&2&3\
4&5&6&6\
8&9&10&11\
12&13&14&15

\end{Bmatrix}
$$
p r e [ i ] [ j ] pre[i][j] pre[i][j]表示从左上角(1,1)到(i,j)的一个矩形的和,比如 p r e [ 2 ] [ 2 ] = 0 + 1 + 4 + 5 = 10 pre[2][2]=0+1+4+5=10 pre[2][2]=0+1+4+5=10

那么 p r e [ i ] [ j ] pre[i][j] pre[i][j]是怎么求来的呢?

我们在计算该数组时,从上往下,从左往右计算,那么当我们计算到 p r e [ i ] [ j ] pre[i][j] pre[i][j]时,它的左上一块矩阵的值就必定是已经计算好了。

好了,现在我们手上有以下三个数据 p r e [ i − 1 ] [ j ] , p r e [ i ] [ j − 1 ] , p r e [ i − 1 ] [ j − 1 ] pre[i-1][j],pre[i][j-1],pre[i-1][j-1] pre[i1][j],pre[i][j1],pre[i1][j1],我们的目标是求出 p r e [ i ] [ j ] pre[i][j] pre[i][j]

根据 p r e pre pre 数组的定义, p r e [ i − 1 ] [ j ] pre[i-1][j] pre[i1][j] 就是红+绿 p r e [ i ] [ j − 1 ] pre[i][j-1] pre[i][j1] 就是红+浅蓝,而 p r e [ i − 1 ] [ j − 1 ] pre[i-1][j-1] pre[i1][j1] 就是,于是我们可以很容易发现:

p r e [ i ] [ j ] = p r e [ i − 1 ] [ j ] + p r e [ i ] [ j − 1 ] − p r e [ i − 1 ] [ j − 1 ] + a [ i ] [ j ] pre[i][j] = pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1]+a[i][j] pre[i][j]=pre[i1][j]+pre[i][j1]pre[i1][j1]+a[i][j]

即:红+绿+红+浅蓝-红+蓝

这样我们就得到了一个 p r e pre pre 数组。当我们用 p r e pre pre 数组去求解我们需要的答案时,其实就是逆向的过程。这里不做讲解,留给读者自己理解。(可以想想,当我们拥有了 p r e pre pre 数组后,该如何求蓝色部分的值?)

类似题目推荐

见博客的知识点专栏中的前缀和&差分专栏

代码

C++

#include <iostream>
#include <cstdio>
using namespace std;

bool matrix[15][15];
int m,n;
int ans=1e9;

bool check(int x,int y,int len){
	for(int i=0;i<len;++i){
		for(int j=0;j<len;++j){
			if(matrix[x+i][y+j]) return false;
		}
	}
	return true;
}

void fill(int x,int y,int len){// 采用异或方法进行填充/取消填充 
	for(int i=0;i<len;++i){
		for(int j=0;j<len;++j){
			matrix[x+i][y+j]^=1;
		}
	}
} 

void dfs(int x, int y, int cnt){
	if(cnt>=ans) return;// 剪枝,当前记录数已经≥已记录的最小答案,不再进行搜索
	if(x==m+1){// 填充完毕,更新答案 
		ans=cnt;
		return;
	}
	if(y>n) dfs(x+1,1,cnt);
	bool full=true;
	for(int i=y;i<=n;++i){// 从当前行的第y个格子开始枚举,找到第一个没有填充的格子 
		if(!matrix[x][i]){// 当前格子未填充,尝试填充正方形 
			full=false;
			for(int j=min(n-i+1,m-x+1);j>=1;--j){// 枚举填充正方形的边长,从长边开始枚举 
				if(check(x,i,j)){// 判断从第x行第i个格子开始能不能填充边长为j的长方形 
					fill(x,i,j);// 填充
					dfs(x,y+j,cnt+1);// 填充完一个正方形,尝试下一次填充
					fill(x,i,j);// 取消填充 
				}
			}
			break;// 尝试在当前格子填充正方形的所有情况已经全部考虑,直接弹出 
		}
	}
	if(full) dfs(x+1,1,cnt);// 当前行都填充了,搜索下一行 
}

int main(){
	scanf("%d %d",&m,&n);
	dfs(1,1,0);
	printf("%d",ans);
	
	return 0;
}

python

def check(x, y, length):
    # 判断从第x行第y个格子开始能否填充边长为length的正方形
    for i in range(length):
        for j in range(length):
            if matrix[x+i][y+j]:
                return False
    return True

def fill(x, y, length):
    # 采用异或方法进行填充/取消填充
    for i in range(length):
        for j in range(length):
            matrix[x+i][y+j] ^= 1

def dfs(x, y, cnt):
    global ans
    if cnt >= ans:
        return  # 剪枝,当前记录数已经≥已记录的最小答案,不再进行搜索
    if x == m + 1:
        ans = cnt  # 填充完毕,更新答案
        return
    if y > n:
        dfs(x + 1, 1, cnt)
    full = True
    for i in range(y, n + 1):
        # 从当前行的第y个格子开始枚举,找到第一个没有填充的格子
        if not matrix[x][i]:  # 当前格子未填充,尝试填充正方形
            full = False
            for j in range(min(n - i + 1, m - x + 1), 0, -1):
                # 枚举填充正方形的边长,从长边开始枚举
                if check(x, i, j):
                    fill(x, i, j)  # 填充
                    dfs(x, y + j, cnt + 1)  # 填充完一个正方形,尝试下一次填充
                    fill(x, i, j)  # 取消填充
            break  # 尝试在当前格子填充正方形的所有情况已经全部考虑,直接跳出循环
    if full:
        dfs(x + 1, 1, cnt)  # 当前行都填充了,搜索下一行

m, n = map(int, input().split())
matrix = [[False] * 15 for _ in range(15)]
ans = 1e9
dfs(1, 1, 0)
print(ans)

Java

import java.util.Scanner;

public class Main {
    static boolean[][] matrix = new boolean[15][15];
    static int m, n;
    static int ans = (int) 1e9;

    static boolean check(int x, int y, int len) {
        // 判断从第x行第y个格子开始能否填充边长为len的正方形
        for (int i = 0; i < len; ++i) {
            for (int j = 0; j < len; ++j) {
                if (matrix[x + i][y + j])
                    return false;
            }
        }
        return true;
    }

    static void fill(int x, int y, int len) {
        // 采用异或方法进行填充/取消填充
        for (int i = 0; i < len; ++i) {
            for (int j = 0; j < len; ++j) {
                matrix[x + i][y + j] ^= true;
            }
        }
    }

    static void dfs(int x, int y, int cnt) {
        if (cnt >= ans)
            return; // 剪枝,当前记录数已经≥已记录的最小答案,不再进行搜索
        if (x == m + 1) {
            ans = cnt; // 填充完毕,更新答案
            return;
        }
        if (y > n)
            dfs(x + 1, 1, cnt);
        boolean full = true;
        for (int i = y; i <= n; ++i) { // 从当前行的第y个格子开始枚举,找到第一个没有填充的格子
            if (!matrix[x][i]) { // 当前格子未填充,尝试填充正方形
                full = false;
                for (int j = Math.min(n - i + 1, m - x + 1); j >= 1; --j) { // 枚举填充正方形的边长,从长边开始枚举
                    if (check(x, i, j)) { // 判断从第x行第i个格子开始能不能填充边长为j的正方形
                        fill(x, i, j); // 填充
                        dfs(x, y + j, cnt + 1); // 填充完一个正方形,尝试下一次填充
                        fill(x, i, j); // 取消填充
                    }
                }
                break; // 尝试在当前格子填充正方形的所有情况已经全部考虑,直接跳出循环
            }
        }
        if (full)
            dfs(x + 1, 1, cnt); // 当前行都填充了,搜索下一行
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        m = scanner.nextInt();
        n = scanner.nextInt();
        dfs(1, 1, 0);
        System.out.println(ans);
        scanner.close();
    }
}

Go

package main

import "fmt"

var matrix [15][15]bool
var m, n int
var ans = int(1e9)

func check(x, y, length int) bool {
	// 判断从第x行第y个格子开始能否填充边长为length的正方形
	for i := 0; i < length; i++ {
		for j := 0; j < length; j++ {
			if matrix[x+i][y+j] {
				return false
			}
		}
	}
	return true
}

func fill(x, y, length int) {
	// 采用异或方法进行填充/取消填充
	for i := 0; i < length; i++ {
		for j := 0; j < length; j++ {
			matrix[x+i][y+j] = !matrix[x+i][y+j]
		}
	}
}

func dfs(x, y, cnt int) {
	if cnt >= ans {
		return // 剪枝,当前记录数已经≥已记录的最小答案,不再进行搜索
	}
	if x == m+1 {
		ans = cnt // 填充完毕,更新答案
		return
	}
	if y > n {
		dfs(x+1, 1, cnt)
	}
	full := true
	for i := y; i <= n; i++ {
		// 从当前行的第y个格子开始枚举,找到第一个没有填充的格子
		if !matrix[x][i] {
			 // 当前格子未填充,尝试填充正方形
			full = false
			for j := min(n-i+1, m-x+1); j >= 1; j-- {
				// 枚举填充正方形的边长,从长边开始枚举
				if check(x, i, j) {
					fill(x, i, j)         // 填充
					dfs(x, y+j, cnt+1)   // 填充完一个正方形,尝试下一次填充
					fill(x, i, j)         // 取消填充
				}
			}
			break // 尝试在当前格子填充正方形的所有情况已经全部考虑,直接跳出循环
		}
	}
	if full {
		dfs(x+1, 1, cnt) // 当前行都填充了,搜索下一行
	}
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func main() {
	fmt.Scan(&m, &n)
	dfs(1, 1, 0)
	fmt.Println(ans)
}

Js

function check(x, y, length) {
  // 判断从第x行第y个格子开始能否填充边长为length的正方形
  for (let i = 0; i < length; i++) {
    for (let j = 0; j < length; j++) {
      if (matrix[x + i][y + j]) {
        return false;
      }
    }
  }
  return true;
}

function fill(x, y, length) {
  // 采用异或方法进行填充/取消填充
  for (let i = 0; i < length; i++) {
    for (let j = 0; j < length; j++) {
      matrix[x + i][y + j] = !matrix[x + i][y + j];
    }
  }
}

function dfs(x, y, cnt) {
  if (cnt >= ans) {
    return; // 剪枝,当前记录数已经≥已记录的最小答案,不再进行搜索
  }
  if (x === m + 1) {
    ans = cnt; // 填充完毕,更新答案
    return;
  }
  if (y > n) {
    dfs(x + 1, 1, cnt);
  }
  let full = true;
  for (let i = y; i <= n; i++) {
    // 从当前行的第y个格子开始枚举,找到第一个没有填充的格子
    if (!matrix[x][i]) {
      // 当前格子未填充,尝试填充正方形
      full = false;
      for (let j = Math.min(n - i + 1, m - x + 1); j >= 1; j--) {
        // 枚举填充正方形的边长,从长边开始枚举
        if (check(x, i, j)) {
          fill(x, i, j); // 填充
          dfs(x, y + j, cnt + 1); // 填充完一个正方形,尝试下一次填充
          fill(x, i, j); // 取消填充
        }
      }
      break; // 尝试在当前格子填充正方形的所有情况已经全部考虑,直接跳出循环
    }
  }
  if (full) {
    dfs(x + 1, 1, cnt); // 当前行都填充了,搜索下一行
  }
}

function main() {
  const readline = require("readline");
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });

  rl.on("line", (line) => {
    const input = line.split(" ").map(Number);
    m = input[0];
    n = input[1];
    rl.close();
  }).on("close", () => {
    matrix = new Array(15).fill(false).map(() => new Array(15).fill(false));
    dfs(1, 1, 0);
    console.log(ans);
    process.exit(0);
  });
}

main();

e = require("readline");
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });

  rl.on("line", (line) => {
    const input = line.split(" ").map(Number);
    m = input[0];
    n = input[1];
    rl.close();
  }).on("close", () => {
    matrix = new Array(15).fill(false).map(() => new Array(15).fill(false));
    dfs(1, 1, 0);
    console.log(ans);
    process.exit(0);
  });
}

main();

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔子哥学算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值