积木
题目描述
小明用积木搭了一个城堡。
为了方便,小明在搭的时候用的是一样大小的正方体积本,搭在了一个 nn 行 mm 列的方格图上,每个积木正好占据方格图的一个小方格。
当然,小明的城堡并不是平面的,而是立体的。小明可以将积木垒在别的积木上面。当一个方格上的积木垒得比较高时,就是一个高塔,当一个方格上没有积木时,就是一块平地。
小明的城堡可以用每个方格上垒的积木层数来表示。例如,下面就表示一个城堡。
9 3 3 1
3 3 3 0
0 0 0 0
这个城堡南面和东面都有空地,西北面有一个大房子,在西北角还有一个高塔,东北角有一个车库。
现在,格格巫要来破坏小明的城堡,他施了魔法水淹小明的城堡。
如果水的高度为 1,则紧贴地面的那些积木要被水淹,在上面的例子中,有 7 块积木要被水淹。
如果水的高度为 2,则更多积木要被水淹,在上面的例子中,有 13 块积木要被水淹。
给定小明的城堡图,请问,水的高度依次为 1,2,3,⋯,H1,2,3,⋯,H 时,有多少块积木要被水淹。
输入描述
输入的第一行包含两个整数 n,mn,m。
接下来 nn 行,每行 mm 个整数,表示小明的城堡中每个位置积木的层数。
接下来包含一个整数 HH,表示水高度的上限。
其中,1≤n,m≤1000,1≤H≤105,积木层数不超过1091≤n,m≤1000,1≤H≤105,积木层数不超过109。
输出描述
输出 HH 行,每行一个整数。第 ii 的整数表示水的高度为 ii时被水淹的积木数量。
分析:首先看到这个题目就应该想到哈希记录每个高度积木的数量,再看到H=1e5,明显的hash,但要注意hash记录的是高度的积木数,不是该层数的积木数,所以要定义一个sum来计算每层积木数
import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
int[] hash = new int[(int)1e5 + 5];
long sumi = 0l;
long ans = 0l;
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
int h = in.nextInt();
if(h>0){
sumi++;
hash[h]++;
}
}
}
//到这里sumi即为第一层的积木数
int H = in.nextInt();
for(int i = 1; i <= H; i++){
ans+=sumi;
System.out.println(ans);
sumi -= hash[i];//减去已经淹没的积木,sumi变成当前层的积木数
}
in.close();
}
}
冰山
题目描述
一片海域上有一些冰山,第 ii 座冰山的体积为 ViVi。
随着气温的变化,冰山的体积可能增大或缩小。第 ii 天,每座冰山的变化量都是 XiXi。当 Xi>0Xi>0 时,所有冰山体积增加 XiXi;当 Xi<0Xi<0 时,所有冰山体积减少 −Xi−Xi;当 Xi=0Xi=0 时,所有冰山体积不变。
如果第 ii 天某座冰山的体积变化后小于等于 00,则冰山会永远消失。
冰山有大小限制 kk。如果第 ii 天某座冰山 jj 的体积变化后 VjVj 大于 kk,则它会分裂成一个体积为 kk 的冰山和 Vj−kVj−k 座体积为 11 的冰山。
第 ii 天结束前(冰山增大、缩小、消失、分裂完成后),会漂来一座体积为 YiYi 的冰山(Yi=0Yi=0 表示没有冰山漂来)。 小蓝在连续的 mm 天对这片海域进行了观察,并准确记录了冰山的变化。
小蓝想知道,每天结束时所有冰山的体积之和(包括新漂来的)是多少。 由于答案可能很大,请输出答案除以 998244353998244353 的余数。
输入描述
输入的第一行包含三个整数 n,m,kn,m,k,分别表示初始时冰山的数量、观察的天数以及冰山的大小限制。
第二行包含 nn 个整数 V1,V2,⋯,VnV1,V2,⋯,Vn,表示初始时每座冰山的体积。 接下来 mm 行描述观察的 mm 天的冰山变化。其中第 ii 行包含两个整数 Xi,YiXi,Yi,意义如前所述。
输出描述
输出 mm 行,每行包含一个整数,分别对应每天结束时所有冰山的体积之和除以 998244353998244353 的余数。
分析:hash加暴力,能通过80%的用例
import java.util.*;
import java.io.*;
public class Main {
static final long MOD = 998244353;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
StringTokenizer st = new StringTokenizer(br.readLine());
// 读取初始冰山数量、观察天数和冰山大小限制
int initialIcebergs = Integer.parseInt(st.nextToken());
int daysObserved = Integer.parseInt(st.nextToken());
int maxSize = Integer.parseInt(st.nextToken());
// 使用HashMap记录冰山体积和对应的数量
Map<Long, Long> volumeCount = new HashMap<>();
// 读取初始冰山体积
st = new StringTokenizer(br.readLine());
for (int i = 0; i < initialIcebergs; i++) {
long volume = Long.parseLong(st.nextToken());
volumeCount.put(volume, volumeCount.getOrDefault(volume, 0L) + 1);
}
// 处理每一天的冰山变化
for (int day = 0; day < daysObserved; day++) {
st = new StringTokenizer(br.readLine());
int change = Integer.parseInt(st.nextToken()); // 体积变化量
int newIceberg = Integer.parseInt(st.nextToken()); // 新冰山体积
// 创建新的临时Map来存储处理后的冰山
Map<Long, Long> newVolumeCount = new HashMap<>();
// 遍历当前所有冰山
for (Map.Entry<Long, Long> entry : volumeCount.entrySet()) {
long currentVolume = entry.getKey();
long count = entry.getValue();
long newVolume = currentVolume + change;
// 如果冰山体积小于等于0,则消失
if (newVolume <= 0) {
continue;
}
// 如果冰山体积超过最大限制,分裂
if (newVolume > maxSize) {
// 分裂为1个体积为maxSize的冰山和(newVolume - maxSize)个体积为1的冰山
newVolumeCount.put((long) maxSize, (newVolumeCount.getOrDefault((long) maxSize, 0L) + count) % MOD);
long ones = (newVolume - maxSize) * count % MOD;
newVolumeCount.put(1L, (newVolumeCount.getOrDefault(1L, 0L) + ones) % MOD);
} else {
// 否则,直接更新体积
newVolumeCount.put(newVolume, (newVolumeCount.getOrDefault(newVolume, 0L) + count) % MOD);
}
}
// 更新当前冰山状态
volumeCount = newVolumeCount;
// 如果有新冰山漂来,添加到当前冰山中
if (newIceberg != 0) {
volumeCount.put((long) newIceberg, (volumeCount.getOrDefault((long) newIceberg, 0L) + 1) % MOD);
}
// 计算当前所有冰山的体积之和
long totalVolume = 0;
for (Map.Entry<Long, Long> entry : volumeCount.entrySet()) {
totalVolume = (totalVolume + entry.getKey() * entry.getValue()) % MOD;
}
// 输出当天的总体积
bw.write(totalVolume + "\n");
}
// 关闭输入输出流
bw.flush();
br.close();
bw.close();
}
}
纯质数
题目描述
本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。
如果一个正整数只有 11 和它本身两个约数,则称为一个质数(又称素数)。
前几个质数是:2,3,5,7,11,13,17,19,23,29,31,37,⋅⋅⋅2,3,5,7,11,13,17,19,23,29,31,37,⋅⋅⋅ 。
如果一个质数的所有十进制数位都是质数,我们称它为纯质数。例如:2,3,5,7,23,372,3,5,7,23,37 都是纯质数,而 11,13,17,19,29,3111,13,17,19,29,31 不是纯质数。当然 1,4,351,4,35 也不是纯质数。
请问,在 11 到 2021060520210605 中,有多少个纯质数?
分析:就纯暴力就行,判断素数的方法一定要记住i <= Math.sqrt(n)这里是<=;
import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
//在此输入您的代码...
int ans = 20210605;
for(int i = 1; i <= 20210605; i++){
if(is_prime(i)){
int x = i;
while(x > 0){
if(is_prime(x%10)){
x/=10;
}else{
ans--;
break;
}
}
}else ans--;
}
System.out.println(1903);
scan.close();
}
public static boolean is_prime(int n){
if(n == 1) return false;
for(int i = 2; i <= Math.sqrt(n); i++){
if(n%i==0){
return false;
}
}
return true;
}
}
完全日期
题目描述
本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。
如果一个日期中年月日的各位数字之和是完全平方数,则称为一个完全日期。
例如:20212021 年 66 月 55 日的各位数字之和为 2+0+2+1+6+5=162+0+2+1+6+5=16,而 1616 是一个完全平方数,它是 44 的平方。所以 20212021 年 66 月 55 日是一个完全日期。
例如:20212021 年 66 月 2323 日的各位数字之和为 2+0+2+1+6+2+3=162+0+2+1+6+2+3=16,是一个完全平方数。所以 20212021 年 66 月 2323 日也是一个完全日期。
请问,从 20012001 年 11 月 11 日到 20212021 年 1212 月 3131 日中,一共有多少个完全日期?
分析:同样暴力,要记住闰年的判断条件
import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改
public class Main {
static int[] mons = {31,28,31,30,31,30,31,31,30,31,30,31};
public static void main(String[] args) {
int ans = 0;
Scanner scan = new Scanner(System.in);
for(int i = 2001; i <= 2021; i++){
if((i%4==0 && i%100!=0) || i%400==0){
mons[1] = 29;
}
for(int j = 1; j <= 12; j++){
for(int k = 1 ; k <= mons[j-1]; k++){
int sum = ret(i) + ret(j) + ret(k);
if(Math.sqrt(sum)==(int)Math.sqrt(sum)){
ans++;
}
}
}
mons[1]=28;
}
System.out.println(ans);
scan.close();
}
public static int ret(int x){
int ret = 0;
while( x > 0){
ret += x%10;
x/=10;
}
return ret;
}
}
最小权值
题目描述
本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。
对于一棵有根二叉树 TT,小蓝定义这棵树中结点的权值 W(T)W(T) 如下:
空子树的权值为 00。
如果一个结点 vv 有左子树 LL, 右子树 RR,分别有 C(L)C(L) 和 C(R)C(R) 个结点,则 W(v)=1+2W(L)+3W(R)+(C(L))2C(R)W(v)=1+2W(L)+3W(R)+(C(L))2C(R)
树的权值定义为树的根结点的权值。
小蓝想知道,对于一棵有 20212021 个结点的二叉树,树的权值最小可能是多少?
分析:推出dp式dp[i] = Math.min(dp[i], 1 + 2 * dp[l] + 3 * dp[r] + (long) l * l * r); 然后暴力
import java.util.Arrays;
// 1:无需package
// 2: 类名必须Main, 不可修改
public class Main {
static long[] dp = new long[2022];
public static void main(String[] args) {
Arrays.fill(dp, Long.MAX_VALUE);
dp[0] = 0;
for (int i = 1; i <= 2021; i++) {
for (int j = 0; j < i; j++) {
int l = j;
int r = i - j - 1;
dp[i] = Math.min(dp[i], 1 + 2 * dp[l] + 3 * dp[r] + (long) l * l * r);
}
}
System.out.println(dp[2021]);
}
}
覆盖
题目描述
本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。
小蓝有一个国际象棋的棋盘,棋盘的大小为 8×88×8,即由 88 行 88 列共 6464 个方格组成。棋盘上有美丽的图案,因此棋盘旋转后与原来的棋盘不一样。
小蓝有很多相同的纸片,每张纸片正好能覆盖棋盘的两个相邻方格。小蓝想用 3232 张纸片正好将棋盘完全覆盖,每张纸片都覆盖其中的两个方格。
小蓝发现,有很多种方案可以实现这样的覆盖。如果棋盘比较小,方案数相对容易计算,比如当棋盘是 2×22×2 时有两种方案,当棋盘是 4×44×4 时有 3636 种方案。但是小蓝算不出他自己的这个 8×88×8 的棋盘有多少种覆盖方案。
请帮小蓝算出对于这个 8×88×8 的棋盘总共有多少种覆盖方案。
分析:不会;横着放好所有方块后,竖着方案是唯一的,只用考虑横着的即可,实际做法:1.先可以进行简单的合法性判定,即一个状态中连续为空的个数是否为奇数,如果为奇数,则无法竖着填满,不合法
import java.util.Arrays;
public class Main {
static final int n = 8, m = 8;
static long[][] dp = new long[9][1 << 8]; // dp[i][j]: 处理到第i列,状态为j的方案数
static boolean[] st = new boolean[1 << 8]; // 标记状态是否合法
public static void main(String[] args) {
// 预处理所有可能的行状态,判断哪些状态是合法的
for (int i = 0; i < (1 << n); i++) {
st[i] = true;
int cnt = 0; // 连续空位的计数
for (int j = 0; j < n; j++) {
if ((i >> j & 1) == 1) { // 如果当前位置被覆盖
if ((cnt & 1) == 1) { // 如果连续空位是奇数个,不合法
st[i] = false;
break;
}
cnt = 0;
} else {
cnt++;
}
}
if ((cnt & 1) == 1) { // 检查最后的连续空位
st[i] = false;
}
}
// 初始化动态规划表
for (int i = 0; i < 9; i++) {
Arrays.fill(dp[i], 0);
}
dp[0][0] = 1; // 初始状态:第0列全空,方案数为1
// 动态规划转移
for (int i = 1; i <= m; i++) { // 遍历每一列
for (int j = 0; j < (1 << n); j++) { // 当前列的状态
for (int k = 0; k < (1 << n); k++) { // 前一列的状态
if ((j & k) == 0 && st[j | k]) { // 状态组合合法
dp[i][j] += dp[i - 1][k];
}
}
}
}
System.out.println(dp[m][0]); // 输出完全覆盖的方案数
}
}
123
题目描述
小蓝发现了一个有趣的数列,这个数列的前几项如下:
1,1,2,1,2,3,1,2,3,4,⋯1,1,2,1,2,3,1,2,3,4,⋯
小蓝发现,这个数列前 11 项是整数 11,接下来 22 项是整数 11 至 22,接下来 33 项是整数 11 至 33,接下来 44 项是整数 11 至 4,依次类推。
小蓝想知道,这个数列中,连续一段的和是多少。
输入描述
输入的第一行包含一个整数 TT,表示询问的个数。
接下来 TT 行,每行包含一组询问,其中第 ii 行包含两个整数 lili 和 riri,表示询问数列中第 lili 个数到第 riri 个数的和。
输出描述
输出 TT 行,每行包含一个整数表示对应询问的答案。
分析:前缀和加二分查找,二分查找这里要注意终止条件
import java.util.Scanner;
public class Main {
static int maxLen = 1414215; // 预估足够大的段数
static long[] a = new long[maxLen]; // a[i]:前i段的总数字个数
static long[] s = new long[maxLen]; // s[i]:前i段所有数字的总和
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
// 预处理a和s数组
a[0] = 0;
for (int i = 1; i < maxLen; i++) {
a[i] = a[i - 1] + i; // 第i段有i个数字
}
s[0] = 0;
for (int i = 1; i < maxLen; i++) {
s[i] = s[i - 1] + a[i]; // 第i段的和刚好是a[i] = 1 + 2 + 3...+i
}
int T = scan.nextInt(); // 查询次数
for (int i = 0; i < T; i++) {
long l = scan.nextLong();
long r = scan.nextLong();
System.out.println(preSum(r) - preSum(l - 1)); // 输出区间和
}
scan.close();
}
// 计算前num个数的和
private static long preSum(long num) {
if (num == 0) return 0;
int l = 0, r = maxLen - 1;
while (l <= r) {
int mid = (l + r ) / 2;
if (a[mid] <= num) {
l = mid + 1;
} else {
r = mid - 1;
}
}
return s[l-1] + a[(int) (num - a[l-1])];
}
}
二进制问题
题目描述
小蓝最近在学习二进制。他想知道 11 到 NN 中有多少个数满足其二进制表示中恰好有 KK 个 11。你能帮助他吗?
输入描述
输入一行包含两个整数 NN 和 KK。
输出描述
输出一个整数表示答案。
分析:记忆化搜索加动态规划,原本以为是简单的排列组合,结果只过了两个样例,发现N会限制每一位上的数,比如N=10,就是1010,那么你就不能出现1100的组合,就相当于你第一位选了1就会限制后面的数,第一位选了0就不会限制,因为少一位肯定不会比N大
// 1:无需package
// 2: 类名必须Main, 不可修改
// public class Main {
// public static void main(String[] args) {
// Scanner scan = new Scanner(System.in);
// int n = scan.nextInt();
// int k = scan.nextInt();
// int count = 0;
// while (n != 0) {
// count++;
// n >>= 1; // 右移一位
// }
// if(count<k){
// System.out.println(0);
// return;
// }
// System.out.println(C(count,k));
// scan.close();
// }
// public static long C(int n, int k){
// long ret = 1;
// for(int i = 0; i < k; i++){
// ret*=n;
// n--;
// }
// return ret;
// }
// }
import java.util.Scanner;
public class Main {
static Long[][][] memo; // 记忆化数组
static int[] bin; // N的二进制数组
static int len; // 数组长度
static int K;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
long N = sc.nextLong();
K = sc.nextInt();
String s = Long.toBinaryString(N);
len = s.length();
bin = new int[len];
for (int i = 0; i < len; i++) {
bin[i] = s.charAt(i) - '0';
}
// 如果K大于二进制位数,直接返回0
if (K > len) {
System.out.println(0);
sc.close();
return;
}
// 初始化记忆化数组:位置、已选1个数(最多K个)、是否受限制
memo = new Long[len][K + 1][2];
long result = dfs(0, 0, 1);
System.out.println(result);
sc.close();
}
/**
* @param pos 当前处理的二进制位(从0开始,0是最高位)
* @param countOne 已经选择的1的个数
* @param limit 是否受到上界限制(1表示还不能超过原N对应位,0表示已不限)
* @return 满足条件的方案数
*/
static long dfs(int pos, int countOne, int limit) {
if (countOne > K) {
return 0; // 已经超过K个1,后续无需处理
}
if (pos == len) {
// 全部位都处理完,判断是否恰好有K个1
return countOne == K ? 1 : 0;
}
if (memo[pos][countOne][limit] != null) {
return memo[pos][countOne][limit];
}
long res = 0;
int up = limit == 1 ? bin[pos] : 1; // 当前可以选的最大数字(0或1)
for (int dig = 0; dig <= up; dig++) {
int newLimit = (limit == 1 && dig == up) ? 1 : 0;
res += dfs(pos + 1, countOne + (dig == 1 ? 1 : 0), newLimit);
}
memo[pos][countOne][limit] = res;
return res;
}
}
异或三角
题目描述
给定 TT 个数 n1,n2,⋯,nTn1,n2,⋯,nT,对每个 nini 请求出有多少组 aa, bb, cc 满足:
- 1≤a,b,c≤ni1≤a,b,c≤ni;
- a⊕b⊕c=0a⊕b⊕c=0,其中 ⊕⊕ 表示二进制按位异或;
- 长度为 a,b,ca,b,c 的三条边能组成一个三角形。
输入描述
输入的第一行包含一个整数 TT。
接下来 TT 行每行一个整数,分别表示 n1,n2,⋯,nTn1,n2,⋯,nT。
输出描述
输出 TT 行,每行包含一个整数,表示对应的答案。
分析:暴力失败,看题解数位dp,完全不懂
import java.util.Arrays;
import java.util.Scanner;
public class XORTriangle {
private static int[] num = new int[32]; // 存储n的二进制表示
private static long[][][] dp = new long[32][2][8]; // DP记忆化数组
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int t = scanner.nextInt();
while (t-- > 0) {
int n = scanner.nextInt();
System.out.println(calculate(n));
}
}
private static long calculate(int n) {
// 初始化DP数组
for (int i = 0; i < 32; i++) {
for (int j = 0; j < 2; j++) {
Arrays.fill(dp[i][j], -1);
}
}
// 将n分解为二进制
int bitCount = 0;
while (n != 0) {
num[++bitCount] = n & 1;
n >>= 1;
}
// 三种情况:a、b、c分别作为最大边
return dfs(bitCount, 1, 0) * 3;
}
private static long dfs(int bitPos, int limit, int state) {
if (bitPos == 0) {
return state == 7 ? 1 : 0; // 必须所有组合都出现过
}
if (dp[bitPos][limit][state] != -1) {
return dp[bitPos][limit][state];
}
long res = 0;
int upperBound = (limit == 1) ? num[bitPos] : 1;
for (int aBit = 0; aBit <= upperBound; aBit++) {
if (aBit == 0) {
// a当前位取0
res += dfs(bitPos - 1, limit & (aBit == upperBound ? 1 : 0), state); // b取0
if (state >= 6) { // 确保(1,1)和(1,0)已出现
res += dfs(bitPos - 1, limit & (aBit == upperBound ? 1 : 0), state | 1); // b取1
}
} else {
// a当前位取1
res += dfs(bitPos - 1, limit & (aBit == upperBound ? 1 : 0), state | 2); // b取0
res += dfs(bitPos - 1, limit & (aBit == upperBound ? 1 : 0), state | 4); // b取1
}
}
return dp[bitPos][limit][state] = res;
}
}
坚持暴力,能过两个样例(
import java.util.Scanner;
public class ImprovedBruteForce {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int T = sc.nextInt();
for (int t = 0; t < T; t++) {
int n = sc.nextInt();
long count = 0;
for (int a = 1; a <= n; a++) {
for (int b = 1; b <= n; b++) {
int c = a ^ b;
// 只考虑范围内的
if (c >= 1 && c <= n) {
// 判断三角形条件
if (a + b > c && a + c > b && b + c > a) {
count++;
}
}
}
}
System.out.println(count);
}
sc.close();
}
}
和与乘积
题目描述
给定一个数列 A=(a1,a2,⋯,an)A=(a1,a2,⋯,an),问有多少个区间 [L,R][L,R] 满足区间内元素的乘积等于他们的和,即 aL⋅aL+1⋯aR=aL+aL+1+⋯+aRaL⋅aL+1⋯aR=aL+aL+1+⋯+aR 。
输入描述
输入第一行包含一个整数 nn,表示数列的长度。
第二行包含 nn 个整数,依次表示数列中的数 a1,a2,⋯,ana1,a2,⋯,an。
输出描述
输出仅一行,包含一个整数表示满足如上条件的区间的个数。
分析:暴力可以过70%的样例
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] A = new int[n];
for (int i = 0; i < n; i++) {
A[i] = sc.nextInt();
}
long count = 0;
int left = 0;
while (left < n) {
long product = 1L;
long sum = 0L;
for (int right = left; right < n; right++) {
product *= A[right];
sum += A[right];
if (product == sum) {
count++;
}
}
left++;
}
System.out.println(count);
}
}
优化后可以过100%
import java.io.IOException;
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) throws IOException {
// 创建Scanner对象读取输入
Scanner sc = new Scanner(System.in);
// 读取数字序列的长度n
int n = sc.nextInt();
// data数组存储原始数据,索引1到n
long data[] = new long[n+1];
// map数组存储前缀和,map[i]表示前i个元素的和
long map[] = new long[n+1];
// no1数组存储所有非1元素的索引位置
int no1[] = new int[n+1];
// bigIndex记录非1元素的数量
int bigIndex = 0;
// 读取数据并预处理
for(int i = 1; i <= n; ++i) {
data[i] = sc.nextInt(); // 读取第i个数字
map[i] = map[i-1] + data[i]; // 计算前缀和
// 如果当前数字不是1,记录其位置
if(data[i] != 1) {
no1[++bigIndex] = i; // no1数组从1开始存储非1元素的位置
}
}
// dif数组:dif[i]表示第i个元素后面连续1的个数
long dif[] = new long[n+1];
// x记录当前连续1的个数
for(int i = n, x = 0; i >= 1; --i) {
dif[i] = x; // 记录当前位置后面连续1的个数
// 更新x的值
if(data[i] != 1) {
x = 0; // 遇到非1元素,重置计数器
} else {
++x; // 遇到1,计数器加1
}
}
// ans记录满足条件的子序列数量(不包括长度为1的)
long ans = 0;
// 遍历所有可能的子序列起始位置l
for(int l = 1; l <= n; ++l) {
long now = data[l]; // now记录当前子序列的乘积,初始为data[l]
// 使用二分查找在no1数组中找到第一个大于等于l的位置
// Arrays.binarySearch返回:
// - 如果找到,返回索引(>=0)
// - 如果没找到,返回(-插入点-1)
int rr = Arrays.binarySearch(no1, 1, bigIndex+1, l);
// 处理二分查找结果
rr = rr >= 0 ? rr + 1 : -rr - 1; // 转换为no1数组中第一个大于l的元素索引
// 遍历所有可能的结束位置r(非1元素)
for(int x = rr; x <= bigIndex; ++x) {
int r = no1[x]; // 获取非1元素的位置
// 如果当前子序列包含多个元素,更新乘积
if(r != l) {
now *= data[r];
}
// 提前终止条件:如果乘积已经超过整个数组的和,后续乘积只会更大
if(now > map[n]) {
break;
}
// 计算子序列[l..r]的和(使用前缀和数组快速计算)
long sum = map[r] - map[l-1];
/* 关键判断条件:
* 1. 乘积 >= 原始和(不含后面连续1的和)
* 2. 乘积 <= 原始和 + 后面连续1的个数(因为每个1会使和增加1)
*
* 这意味着我们可以通过在子序列后面添加k个连续1(0 <= k <= dif[r])
* 来使和等于乘积
*/
if(now >= sum && now <= sum + dif[r]) {
++ans; // 满足条件,计数器加1
}
}
}
// 最终结果:长度为1的子序列有n个(都满足条件)
// 加上其他满足条件的子序列数量ans
System.out.println(n + ans);
}
}

1983

被折叠的 条评论
为什么被折叠?



