我们可以证明,对于前 i 个数构成的所有区间,其“数字凸包区间”的并恰好是一个连续区间 [Pmin,Pmax](其中
Pmin=min{a1,…,ai},Pmax=max{a1,…,ai}
)。这是因为整个区间 [1,i] 作为一个子区间,其数字凸包就是 [Pmin,Pmax];而其他子区间给出的区间都是这个区间的子区间,不可能扩充出整段连续区间之外的数值。
因此,对每个 i 我们只需维护前缀中的最小值和最大值,从而:
- 若 Pmin>0(也就是说前缀中没有出现0),那么整个集合不包含从 0 到 Pmin−1 的数,此时最小的非负整数就是 0。
- 否则(也即 Pmin=0),那么由全区间 [0,Pmax] 可知所有从 0 到 Pmax 都被覆盖,答案就是 Pmax+1。
下面给出Java实现,时间复杂度 O(n):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.StringTokenizer;
public class Main { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String line = br.readLine(); int n = Integer.parseInt(line.trim()); int[] a = new int[n]; StringTokenizer st = new StringTokenizer(br.readLine()); for (int i = 0; i < n; i++) { a[i] = Integer.parseInt(st.nextToken()); } int prefixMin = Integer.MAX_VALUE; int prefixMax = Integer.MIN_VALUE; StringBuilder sb = new StringBuilder(); for (int i = 0; i < n; i++) { prefixMin = Math.min(prefixMin, a[i]); prefixMax = Math.max(prefixMax, a[i]); if (prefixMin > 0) { sb.append("0"); } else { sb.append(prefixMax + 1); } if (i < n - 1) { sb.append(" "); } } System.out.println(sb.toString()); } }
|
代码说明
输入处理
使用 BufferedReader 和 StringTokenizer 提高大数据量时的读入效率。
维护前缀最值
循环中持续维护前 i 个数的最小值 (prefixMin) 和最大值 (prefixMax)。
判断与输出
- 当
prefixMin > 0 时,说明前缀中没有0(也就没有比0更小的非负数),答案输出 0。
- 当
prefixMin == 0 时,根据区间覆盖情况(由整段子区间 [0,prefixMax] 可知),答案输出 prefixMax + 1。
这种做法只对每个 i 进行常数时间操作,总体时间复杂度为 O(n),能够满足 n≤2×105 的要求。
题目描述
给定一个长度为 n 的二进制字符串 s,由 0 和 1 组成。我们需要构建一个行数为 n,列数为 n 的方表,由 0 和 1 组成。第一行为原始字符串 s,第二行为字符串 s 向右循环移动一个位置,第三行为字符串 s 向右循环移动两个位置,以此类推。
求表中所有由 0 组成的三角形或矩形的最大面积值。
输入描述
输入一个长度为 n 的二进制字符串 s,仅包含 0 和 1 字符,其中 1≤n≤5000。
输出描述
输出表中所有由 0 组成的三角形或矩形的最大面积值。
示例 1
输入
输出
我们可以证明,由于构造表的方式非常特殊,每一行都是原始二进制串的右循环移位,表中任意一个形状(严格说是其“数字凸包”,也就是该形状中所有单元格对应 j−i(取模 n)组成的区间)的状态只依赖于原串中某个在环上连续的零段。经过简单分析可以证明:
- 若我们选取一个“矩形”(即连续 h 行、连续 w 列)的子区域,其所有单元格对应的 j−i值集合为
{D−(h−1),D−h+2,…,D+w−1}
(其中 D 为某个偏移量),这实际上是一个长度为 w+h−1 的整数区间(注意区间中相邻两行之间会有重叠)。显然要使区域内全为 0,必须原串中存在一个(环上连续的)零段长度至少为 w+h−1;而在非全零(即 w+h−1<n)的情形中,经过取最优选择可以证明最大矩形面积为
R(L)=⌊2L+1⌋⋅⌈2L+1⌉,
其中 L 表示原串(视为环状)中零的最长连续段长度。
- 同理,设我们只允许“直角三角形”形状(例如以左上角为直角,每往下一行比上一行多 1 个单元,面积为 1+2+⋯+h=2h(h+1)),那么设三角形高为 h,它“用到”的j−i值正好组成一个长度为 h 的区间,因此需要 h≤L;这样最大的三角形面积为
T(L)=2L(L+1).
很容易验证,当 L≥2 时
T(L)=2L(L+1)>⌊2L+1⌋⋅⌈2L+1⌉=R(L),
即非全零情况下最佳面积取决于“零三角形”形状。注意:若原串全为 0,则表中所有单元均为 0,这时当然最大面积为整个表面积 n×n(因为 n2>2n(n+1) )。
综上,我们可以先扫描原串(按环状计)求出最长连续 0 的长度 L(如果不存在 0,则答案为 0);接下来判断:
- 若 L=n(全零串),答案为 n2;
- 否则答案为 2L(L+1)(即构成某个“零三角形”的面积最大值)。
下面给出 Java 代码实现,时间复杂度 O(n):
java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader;
public class Main { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String s = br.readLine().trim(); int n = s.length(); int L = 0; int curr = 0; for (int i = 0; i < n; i++) { if (s.charAt(i) == '0') { curr++; if (curr > L) { L = curr; } } else { curr = 0; } } if (n > 0 && s.charAt(0) == '0' && s.charAt(n - 1) == '0') { int prefix = 0, suffix = 0; for (int i = 0; i < n && s.charAt(i) == '0'; i++) { prefix++; } for (int i = n - 1; i >= 0 && s.charAt(i) == '0'; i--) { suffix++; } if (prefix + suffix > L) { L = prefix + suffix; } if (L > n) { L = n; } } long ans = 0; if (L == 0) { ans = 0; } else if (L == n) { ans = (long) n * n; } else { ans = (long) L * (L + 1) / 2; } System.out.println(ans); } }
|
代码说明
求最长连续 0 段(环状)
先正向扫描统计连续 0 数;此外注意如果串首和串尾都是 0,则它们在环状上可以连在一起,故额外计算首部和尾部 0 的个数,并更新最长连续数 L(但 L 最大不超过 n)。
判断答案
- 若 L=0(没有 0),答案为 0。
- 若 L=n(全 0 串),整表全为 0,答案为 n×n;
- 否则答案为 2L(L+1)(这正是构成某个“零三角形”时所能达到的最大面积)。
这样我们就实现了在 O(n) 内求解表中所有由 0 组成的(三角形或矩形)区域的最大面积值。
小结
由于表中每个单元的值仅取决于 s[(j−i)modn],整个问题转化为求原串(环状)中最长的零段长度 L;并证明在非全零情况下,“零直角三角形”的面积为
2L(L+1),
始终大于同样受限制的矩形面积。因此最终答案为
- 如果 s 全 0:答案为 n2;
- 否则:答案为 2L(L+1)。
这种思路不仅降低了时间复杂度(仅 O(n)),同时也利用了构造矩阵时的“循环移位”这一特殊性质。
题目描述
米小游拿到了一个数组,她有若干次询问,每次询问输入一个 x,她希望你判断 x 是否能由数组中的两个元素相乘得出。用数学语言描述,你需要寻找到两个下标 i 和 j(i<j),满足 ai×aj=x。
输入描述
第一行输入一个正整数 n,代表数组的大小。
第二行输入 n 个正整数 ai,代表数组的元素。
第三行输入一个正整数 q,代表询问次数。
接下来的 q 行,每行输入一个正整数 x,代表一次询问。
- 1≤n,q≤105
- 1≤ai,x≤106
输出描述
对于每次询问,如果无法找到两数乘积等于 x,输出 -1 -1。
否则输出 i 和 j(i<j),用空格隔开,代表 ai×aj=x。有多解时输出任意即可。
示例 1
输入
basic
输出
basic
说明
- 第一组询问,输出
1 5 也是可以的。
- 第二组询问,显然无法找到两个元素相乘等于 5。
下面给出 Java 实现。思路是:预处理数组中每个数字在数组中第一次出现和第二次出现的位置,下标记为 1-indexed。这样,对于两个数字 a 和 b 来说:
- 若 a\=b 只需检查数组中是否均出现过;
- 若 a=b 则需要出现两次以上。
对于每次询问 x ,我们枚举 d 从 1 到 x(即所有可能的因子),若 d 能整除 x 则候选另一因子为 d2=x/d。对候选对:
- 若 d==d2,必须保证数组中该数字出现次数至少为 2。
- 否则只要数组中同时出现了 d 和 d2。
输出时注意需满足 i<j 的顺序,输出时将两个下标排序后输出。
时间复杂度:每次询问最多枚举 x 个因子,由于 x≤1×106 所以每次最多约 1000 次循环,整体时间复杂度为 O(qx) 。
下面给出完整代码:
java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.StringTokenizer;
public class Main { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st;
st = new StringTokenizer(br.readLine()); int n = Integer.parseInt(st.nextToken());
int MAX = 1000000; int[] firstIndex = new int[MAX + 1]; int[] secondIndex = new int[MAX + 1];
st = new StringTokenizer(br.readLine()); for (int i = 1; i <= n; i++) { int num = Integer.parseInt(st.nextToken()); if (firstIndex[num] == 0) { firstIndex[num] = i; } else if (secondIndex[num] == 0) { secondIndex[num] = i; } } int q = Integer.parseInt(br.readLine().trim()); StringBuilder sb = new StringBuilder(); for (int qi = 0; qi < q; qi++) { int x = Integer.parseInt(br.readLine().trim()); boolean found = false; int sqrtX = (int) Math.sqrt(x); for (int d = 1; d <= sqrtX; d++) { if (x % d != 0) { continue; } int d2 = x / d; if (d > MAX || d2 > MAX) { continue; } if (d == d2) { if (firstIndex[d] != 0 && secondIndex[d] != 0) { sb.append(firstIndex[d]).append(" ").append(secondIndex[d]).append("\n"); found = true; break; } } else { if (firstIndex[d] != 0 && firstIndex[d2] != 0) { int i1 = firstIndex[d]; int i2 = firstIndex[d2]; if (i1 > i2) { int temp = i1; i1 = i2; i2 = temp; } sb.append(i1).append(" ").append(i2).append("\n"); found = true; break; } } } if (!found) { sb.append("-1 -1\n"); } } System.out.print(sb.toString()); } }
|
代码说明
预处理
对于给定的数组,我们利用两个数组 firstIndex 和 secondIndex 分别记录每个数字第一次和第二次出现的位置,方便后续判断同一个数字能否作为一对两个相乘相等于 x 的候选。
查询处理
对每个查询 x,枚举 1≤d≤x:
- 若 d 不是 x 的因数,跳过;
- 当 d 正好为平方根(即 d=x/d)时判断是否出现至少两次;
- 否则判断数字 d 与 x/d 是否均在数组中出现。
输出
如果找到满足条件的因子对,则输出它们在数组中的下标(确保 i<j);如果所有候选中均没有满足,则输出 -1 -1 。
该算法总体时间复杂度为 O(qx),足以应对 q≤105 的情形。