图形排版
小明需要在一篇文档中加入 N 张图片,其中第张图片的宽度是Wi,高度是 Hi。
假设纸张的宽度是M,小明使用的文档编辑工具会用以下方式对图片进行自动排版:
-
该工具会按照图片顺序,在宽度 M以内,将尽可能多的图片排
在一行。该行的高度是行内最高的图片的高度。例如在 M10
的纸张上依次打印 3x4,2x2,3x3 三张图片,则效果如下图所示,这
行高度为4。(分割线以上为列标尺,分割线以下为排版区域;数
字组成的矩形为第a 张图片占用的版面
111
111-----333
111 22 333
111 22 333 -
如果当前行剩余宽度大于 0,并且小于下一张图片,则下一张图
片会按比例缩放到宽度为当前行剩余宽度(高度向上取整),然后放
入当前行。例如再放入一张 4x9 的图片,由于剩余宽度是2,这张
图片会被压缩到2x5,再被放入第一行的末尾。此时该行高度为6
---------------44
111-----------44
111-----333 44
111 22 333 44
111 22 333 44 -
如果当前行剩余宽度为 0,该工具会从下一行开始继续对剩余的
图片进行排版,直到所有图片都处理完毕。此时所有行的总高度和
就是这N张图片的排版高度。例如再放入 11x1,5x5,3x4的图片
后,效果如下图所示,总高度为 11:
----------------44
111-----------44
111---- 333 44
111 22 333 44
111 22 333 44
5 555 555 555
6 666 6
6 666 6 7 7 7
6 666 6 7 7 7
6 666 6 7 7 7
6 666 6 7 7 7
现在由于排版高度过高,图片的先后顺序也不能改变,小明只好从
V 张图片中选择一张删除掉以降低总高度。他希望剩余N1张
图片按原顺序的排版高度最低,你能求出最低高度是多少么?
输入描述
第一行包含两个整数 MN,分别表示纸张宽度和图片的数量
接下来N行,每行2个整数W,H,表示第个图大小为
Wix Hi。
其中,1< N< 105,1<M,Wi, Hi< 100
输出描述
个整数,
表示在删除掉某一张图片之后,排版高度最少能是多
少
输入
4 3
2 2
2 3
2 2
输出
2
运行限制
- 最大运行时间: 2s
- 最大运行内存:256M
题目意思
将N张图片进行排版后,从中抽取一张出来,使得总高度最小,并且图片的顺序不变,例如:12345
67----
从中抽取3后,排序变为
12456
7-------
暴力 O(n2)
将每一张图片被删除后排版的高度列举出来,选出最小的高度。
但是只能过部分测试用例。
package lanqiao;
import java.util.Scanner;
public class Mar15 {
static int n = 100000+10;
static int M, N;
static int[] W = new int[n];
static int[] H = new int[n];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
M = sc.nextInt();
N = sc.nextInt();
for(int i = 0; i < N; i++) {
W[i] = sc.nextInt();
H[i] = sc.nextInt();
}
int ans = Integer.MAX_VALUE; // 将一个int型最大值赋值给ans,用于后面选出最小高度
for(int i = 0; i < N; i++) { // 依次遍历每张图片,被遍历到的图片即为被删除的图片
int h = cal(i);
// System.out.println("第"+i+"个:"+ h);
ans = Math.min(h,ans);
}
System.out.println(ans);
}
/*
* cal方法用于将图片进行排版,返回排版后的高度
* 接收参数i,表示第i个图形,在排版的过程中遇到第i个图形就跳过,相当于将第i个图形删除
*/
static int cal(int i) {
int sum = 0, j = 0;
while(j < N) {
int w = 0, h = 0, temp;
while(w < M && j < N) {
if(j == i) {
j++;
continue; // 遇到第i个图形就跳过
}
if(w + W[j] > M) {
temp = ( (H[j] * (M - w) - 1)/ W[j] )+ 1; // 向上取整,(x-1/y) + 1
} else {
temp = H[j];
}
h = Math.max(h, temp);
w += W[j];
j++;
}
// System.out.print(h + " ");
sum += h;
}
return sum;
}
}
测试结果:
优化后
排版后的图片,从中抽取一张删除,可以发现可以将图片分成三部分:删除图片所在行、删除图片所在行的上面部分,删除图片所在行的下面部分
如下图:左侧,绿色代表需要删除的图片,将图片删除后如右侧所示
紫色部分不动,蓝色部分一些图片往红色一行补充
排版的高度等于上面行高度+删除图片所在行高度+下面行高度
因此我们依次遍历图片Pi ,用变量pre_h保存紫色部分的高度,用cal()计算删除图片所在行的高度+下面行的高度,用h保存,这样ans就取min(pre_h + h, ans),ans就是所求的最小高度
一、cal(int w, int h, int i)
我们需要一个方法,接收参数i,返回i所在行的高度+下面行的高度。
参数i作为起始图片,找到图片j(i <= j < N),图片j是i所在行最后一个图片,或者是最后一个图片(N-1),那么 j+1就是下一行的第一个图片,或者是N,假设用h存储i所在行的高度最大值,此外有个t[]数组存储着第k个(0<= k <= N)图片为开头的图片排版后的高度,那么我们就可以return h + t [j+1]
只有参数i还不行,注意看上面左侧图,假设删除图片为Pi ,i左边红色部分宽度需要用temp_w保存,左边红色部分图片中高度最大值需要用temp_h保存,并传入cal(),这样我们只需要计算第i+1个图片,i左边的图片用temp_w, temp_h来约束。
二、t[]
t数组存储的是第k个(0<= k <= N)图片为开头的图片排版后的高度
如图:
第一行以第1张图片开头,所以排版后的高度是t[0],第二行以第5张图片开头,所以从第二行以第5张图片为开头排版后的高度是t[4],t[0] - t[4]可以得到第一行的高度
如图:
删除第1张图片后,以第2张图片为开头的高度为t[1],以第6张图片为开头的高度为t[5]。
t数组初始化从后往前遍历每张图片
如图,从图片排版后面部分截取了几张图片1~8:
t[N]为0,从N-1(最后一张图片)开始。若要计算t[1],则计算1所在行的高度h,以及下面行的高度t[5](因为下面是以编号5这张图片作为开头的)
P
S
:
数组索引随便取的,看得懂就行
\textcolor{red}{PS:数组索引随便取的,看得懂就行}
PS:数组索引随便取的,看得懂就行
代码如下:
package lanqiao;
import java.util.Scanner;
/**
* 图形转换,自我总结
* @author yu
* @date 23/03/2024 10:19 AM
*/
public class March23 {
static int M, N;
static int NN = (int) Math.pow(10,5) + 10;
static int[] W = new int[NN];
static int[] H = new int[NN];
static int[] t = new int[NN];
public static void main(String[] args) {
int h, temp_w = 0, temp_h = 0, ans = Integer.MAX_VALUE, pre_h = 0;
Scanner sc = new Scanner(System.in);
M = sc.nextInt();
N = sc.nextInt();
for(int i = 0; i < N; i++){
W[i] = sc.nextInt();
H[i] = sc.nextInt();
}
for(int i = N-1; i >= 0; i--) {
t[i] = cal(0,0, i); //将每个图片作为第一行第一张时的总高度存入t数组
}
for(int i = 0; i < N; i++) {
h = cal(temp_w, temp_h, i+1);
ans = Math.min(pre_h + h, ans);
if(temp_w + W[i] > M) {
int hh = (H[i] * (M - temp_w) - 1 )/ W[i] + 1; //压缩高度并向上取整
temp_h = Math.max(temp_h, hh);
} else {
temp_h = Math.max(temp_h, H[i]);
}
temp_w += W[i];
if(temp_w >= M) { // 宽度已经超过M了,该换到下一行,temp_w 和 temp_h 该置零
pre_h += temp_h;
temp_w = 0;
temp_h = 0;
}
}
System.out.println(ans);
sc.close();
}
public static int cal(int w, int h, int i) {
int j = i, hh = h, ww = w;
while(j < N && w < M) {
if(w + W[j] > M) {
h = (H[j]*(M-w) - 1) / W[j] + 1;// 向上取整
} else {
h = H[j];
}
w += W[j];
hh = Math.max(h,hh);
j++;
}
return hh + t[j]; //当前行高度加上剩余行的高度
}
}
向上取整公式记得加括号,(x-1) / y + 1,原本是
x
−
1
y
+
1
\frac{x-1}{y} +1
yx−1+1 去掉括号后就变成了
x
−
1
y
+
1
x -\frac{1}{y} +1
x−y1+1
最后
写这篇文章主要是来捋一下自己的思路,看了别人通过的代码后自己试着手敲出来。
虽然写的像是一坨,但如果能帮到你的话,帮忙点个赞吧,嘿嘿