#P1519. 第1题-小红划分
-
1000ms
Tried: 653
Accepted: 93
Difficulty: 6
所属公司 :
京东
时间 :2023年9月2日
-
算法标签>二分算法
第1题-小红划分
思路:二维前缀和+二分
这个问题其实就是在求一个靠近 2sum 的最近的数,不过这个数是由正方形中的数的和组成的。
那么可以考虑枚举正方形的左上角端点,然后二分正方形的边长,找到一个正方形数之和大于等于 2sum 的最小边长。
而这里判断时需要快速求出一个正方形内数的和,可以通过预处理二维前缀和,然后查询可以O(1)计算出。
时间复杂度:O(nmlogn)
代码
C++
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1010;
ll pre[N][N];
int n, m;
ll get(int a, int b, int c, int d) {
return pre[c][d] - pre[c][b - 1] - pre[a - 1][d] + pre[a - 1][b - 1];
}
ll myabs(ll a) {
if (a >= 0) return a;
return -a;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
int x; scanf("%d", &x);
pre[i][j] = pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1] + x;
}
ll sum = pre[n][m];
ll half = (sum + 1) / 2;
ll ans = sum;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
// 正方形左上角为 (i, j)
int l = 1, r = min(n - i + 1, m - j + 1);
// 找到大于等于 half 的最小的边长
while (l < r) {
int mid = (l + r) >> 1;
if (get(i, j, i + mid - 1, j + mid - 1) >= half) {
r = mid;
} else {
l = mid + 1;
}
}
ans = min(ans, myabs(sum - 2ll * get(i, j, i + l - 1, j + l - 1)));
if (l > 1) {
l -= 1;
ans = min(ans, myabs(sum - 2ll * get(i, j, i + l - 1, j + l - 1)));
}
}
cout << ans << "\n";
return 0;
}
Java
import java.util.*;
public class Main{
static int N = 1010;
static long[][] g = new long[N][N];
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
g[i][j] = in.nextInt();
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
g[i][j] += g[i-1][j] + g[i][j-1] - g[i-1][j-1];
}
}
long sum = g[n][m];
long half = (sum + 1) / 2;
long ans = sum;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int l = 1, r = Math.min(n - i + 1, m - j + 1);
while(l < r) {
int mid = l + ((r - l) >> 1 );
if(get(i, j, i + mid - 1, j + mid - 1) >= half){
r = mid;
}else {
l=mid + 1;
}
}
ans = Math.min(ans, myabs(sum - 2 * get(i, j, i + l - 1, j + l - 1)));
if (l > 1) {
l -= 1;
ans = Math.min(ans, myabs(sum - 2 * get(i, j, i + l - 1, j + l - 1)));
}
}
}
System.out.println(ans );
}
private static long myabs(long x) {
return Math.abs(x);
}
private static long get(int a, int b, int c, int d) {
return g[c][d] - g[c][b-1] - g[a-1][d] + g[a-1][b-1];
}
}
Python
import sys
from collections import *
from functools import *
n,m=list(map(int,sys.stdin.readline().strip().split()))
mat=[]
for i in range(n):
mat.append(list(map(int,sys.stdin.readline().strip().split())))
target=sum(sum(i) for i in mat)/2
summ=[[0]*(m+1) for i in range(n+1)]
for i in range(n):
for j in range(m):
summ[i+1][j+1]=mat[i][j]+summ[i][j+1]+summ[i+1][j]-summ[i][j]
minn=10**14
def check(mid):
return (summ[i+1][j+1]-summ[i+1-mid][j+1]-summ[i+1][j+1-mid]+summ[i+1-mid][j+1-mid])<=target
def get(i1,j1,i2,j2):
return summ[i2+1][j2+1]-summ[i1][j2+1]-summ[i2+1][j1]+summ[i1][j1]
for i in range(n):
for j in range(m):
left,right=0,min(i+1,j+1)
while left<=right:
mid=(left+right)//2
if check(mid):
left=mid+1
else:
right=mid-1
minn=min(minn,abs(target-get(i-right+1,j-right+1,i,j)),abs(target-get(i-right,j-right,i,j)) if i-right>=0 else 10**12)
print(int(minn*2))
题目描述
先前,小红有一个数组,要求这个数组中选择若干个数,使得选出的数之和与剩下的数之和的差值绝对值尽可能小。
现在,问题升级了,小红有一个 n×m 的矩阵,要求从这个矩阵中选择出一个正方形,使得正方形中的数之和与剩下的数之和的差值绝对值尽可能小。
你能帮帮小红吗?
输入描述
第一行,两个正整数 n 和 m(1≤n,m≤1000) ,表示矩阵的行数和列数
接下来的 n 行,每行有 m 个正整数,第 i 行的第 j 个正整数为 ai,j(1≤ai,j≤10000)
输出描述
一个整数,表示正方形中的数之和与剩下的数之和的差值绝对值的最小值。
样例
输入
3 3
9 9 8
2 4 4
3 5 3
输出
1
说明
选择 a[1][1], a[1][2], a[2][1], a[2][2] 构成的长度为 2 的正方形,这些数和为 24 ,剩下的数和为 23 ,此时差值绝对值最小,为 1 。
Related
In following contests: