问题描述
在库存管理系统中,跟踪和调节商品库存量是关键任务之一。小蓝经营的仓库中存有多种商品,这些商品根据类别和规格被有序地分类并编号,编号范围从 1
至 n
。初始时,每种商品的库存量均为 0
。
为了高效地监控和调整库存量,小蓝的管理团队设计了 m
个操作,每个操作涉及到一个特定的商品区间,即一段连续的商品编号范围(例如区间 [L, R]
)。执行这些操作时,区间内每种商品的库存量都将增加 1
。
然而,在某些情况下,管理团队可能会决定不执行某些操作,使得这些操作涉及的商品区间内的库存量不会发生改变,维持原有的状态。
现在,管理团队需要一个评估机制,来确定如果某个操作未被执行,那么最终会有多少种商品的库存量为 0
。对此,请你为管理团队计算出,每个操作未执行时,库存量为 0
的商品的种类数。
输入格式
- 第一行包含两个整数
n
和m
,分别表示商品的种类数和操作的个数。 - 接下来的
m
行,每行包含两个整数L
和R
,表示一个操作涉及的商品区间。
输出格式
输出格式:
输出共 m
行,每行一个整数,第 i
行的整数表示如果不执行第 i
个操作,则最终库存量为 0
的商品种类数。
样例输入
5 3
1 2
2 4
3 5
样例输出
1
0
1
样例说明
考虑不执行每个操作时,其余操作对商品库存的综合影响:
- 不执行操作 1:剩余的操作是操作 2(影响区间
[2, 4]
)和操作 3(影响区间[3, 5]
)。执行这两个操作后,商品库存序列变为[0, 1, 2, 2, 1]
。在这种情况下,只有编号为1
的商品的库存量为0
。因此,库存量为0
的商品种类数为1
。 - 不执行操作 2:剩余的操作是操作 1(影响区间
[1, 2]
)和操作 3(影响区间[3, 5]
)。执行这两个操作后,商品库存序列变为[1, 1, 1, 1, 1]
。在这种情况下,所有商品的库存量都不为0
,因此,库存量为0
的商品种类数为0
。 - 不执行操作 3:剩余的操作是操作 1(影响区间
[1, 2]
)和操作 2(影响区间[2, 4]
)。执行这两个操作后,商品库存序列变为[1, 2, 1, 1, 0]
。在这种情况下,只有编号为5
的商品的库存量为0
。因此,库存量为0
的商品种类数为1
。
评测用例规模与约定
- 对于 20% 的评测用例,
1 ≤ n, m ≤ 5 × 10^3
,1 ≤ L ≤ R ≤ n
- 对于所有评测用例,
1 ≤ n, m ≤ 3 × 10^5
,1 ≤ L ≤ R ≤ n
c++代码(差分法)
#include<bits/stdc++.h>
#include<stdio.h>using namespace std;int n, m, cont = 0;
vector<int> dir, dp, arr;
vector<vector<int>> ops;void add(int left, int right) {dir[left]++;if (right + 1 <= n) dir[right + 1]--;
}int main() {scanf("%d %d", &n, &m);dir = vector<int>(n + 1, 0);arr = vector<int>(n + 1, 0);dp = vector<int>(n + 1, 0);ops = vector<vector<int>>(m, vector<int>(2));for (int i = 0; i < m; i++) {scanf("%d %d", &ops[i][0], &ops[i][1]);add(ops[i][0], ops[i][1]);}for (int i = 1; i <= n; i++) {arr[i] = dir[i] + arr[i - 1];if (arr[i] == 0) cont++;if (arr[i] == 1) dp[i] = dp[i - 1] + 1;else dp[i] = dp[i - 1];}for (int i = 0; i < m; i++) {int left = ops[i][0], right = ops[i][1];int k = dp[right] - dp[left - 1];printf("%d\n", cont + k);}return 0;
}//by wqs
c++代码(线段树法)
由于数组初始状态数值本来就为0,所以不需要build函数。
#include<bits/stdc++.h>
#include<stdio.h>using namespace std;int n, m, a, b, cont = 0;class tree {
public:int val;int lazy;
};vector<tree> trees;
vector<vector<int>> ops;
vector<int> arr, dp;void passlazy(int p, int l, int r) {int mid = (l + r) / 2;if (trees[p].lazy > 0) {trees[2 * p].lazy += trees[p].lazy;trees[2 * p + 1].lazy += trees[p].lazy;trees[2 * p].val += (mid - l + 1) * trees[p].lazy;trees[2 * p + 1].val += (r - mid) * trees[p].lazy;trees[p].lazy = 0;}
}void add(int p, int l, int r, int left, int right) {if (l >= left && r <= right) {trees[p].val += r - l + 1;trees[p].lazy += 1;return;}passlazy(p, l, r);int mid = (l + r) / 2;if (left <= mid) add(2 * p, l, mid, left, right);if (right > mid) add(2 * p + 1, mid + 1, r, left, right);trees[p].val = trees[2 * p].val + trees[2 * p + 1].val;
}int quary(int p, int l, int r, int left, int right) {if (l >= left && r <= right) return trees[p].val;passlazy(p, l, r);int mid = (l + r) / 2, ans = 0;if (left <= mid) ans += quary(2 * p, l, mid, left, right);if (right > mid) ans += quary(2 * p + 1, mid + 1, r, left, right);return ans;
}int main() {scanf("%d %d", &n, &m);trees = vector<tree>(4 * n);arr = vector<int>(n + 1);dp = vector<int>(n + 1, 0);ops = vector<vector<int>>(m, vector<int>(2));for (int i = 0; i < m; i++) {scanf("%d %d", &ops[i][0], &ops[i][1]);add(1, 1, n, ops[i][0], ops[i][1]);}for (int i = 1; i <= n; i++) {arr[i] = quary(1, 1, n, i, i);}for (int i = 1; i <= n; i++) {if (arr[i] == 0) cont++;if (arr[i] == 1) dp[i] = dp[i - 1] + 1;else dp[i] = dp[i - 1];}for (int i = 0; i < m; i++) {int left = ops[i][0], right = ops[i][1];int k = dp[right] - dp[left - 1];printf("%d\n", k + cont);}
}//by wqs
算法解析
得到m次区间加1后的数组
有两种比较好的办法,第一种办法是差分,大致代码如下
void add(int left, int right) {dir[left]++;if (right + 1 <= n) dir[right + 1]--;
}for (int i = 0; i < m; i++) {scanf("%d %d", &left, &right);add(left, right);
}for (int i = 1; i <= n; i++) {arr[i] = dir[i] + arr[i - 1];
}
第二种办法是线段树
由于数组初始状态数值本来就为0,所以不需要build函数。
class tree {
public:int val;int lazy;
};
vector<tree> trees;void passlazy(int p, int l, int r) {int mid = (l + r) / 2;if (trees[p].lazy > 0) {trees[2 * p].lazy += trees[p].lazy;trees[2 * p + 1].lazy += trees[p].lazy;trees[2 * p].val += (mid - l + 1) * trees[p].lazy;trees[2 * p + 1].val += (r - mid) * trees[p].lazy;trees[p].lazy = 0;}
}void add(int p, int l, int r, int left, int right) {if (l >= left && r <= right) {trees[p].val += r - l + 1;trees[p].lazy += 1;return;}passlazy(p, l, r);int mid = (l + r) / 2;if (left <= mid) add(2 * p, l, mid, left, right);if (right > mid) add(2 * p + 1, mid + 1, r, left, right);trees[p].val = trees[2 * p].val + trees[2 * p + 1].val;
}int quary(int p, int l, int r, int left, int right) {if (l >= left && r <= right) return trees[p].val;passlazy(p, l, r);int mid = (l + r) / 2, ans = 0;if (left <= mid) ans += quary(2 * p, l, mid, left, right);if (right > mid) ans += quary(2 * p + 1, mid + 1, r, left, right);return ans;
}for (int i = 0; i < m; i++) {scanf("%d %d", &left, &right);add(1, 1, n, left, right);
}
for (int i = 1; i <= n; i++) {arr[i] = quary(1, 1, n, i, i);
}
这两种办法的时间复杂度差不多,编写难度也差不多,只是线段树代码量大。
求出所有加一操作后数值为0的有多少个
if (arr[i] == 0) cont++;
用动态规划求某个区间有多少个1
//dp[i]表示前i个数字1的个数
if (arr[i] == 1) dp[i] = dp[i - 1] + 1;
else dp[i] = dp[i - 1];
//left到right区间1的个数k=dp[right] - dp[left - 1];
直接得出答案
//这样不执行[left, right]的加一操作,会有k + cont个0
//k = dp[right] - dp[left - 1];
//很容易证明,你给[left, right]每个数减一,原来的1会变成0,原来的0已经包含在cont里面,所有我们只关心里面有多少1
for (int i = 0; i < m; i++) {int left = ops[i][0], right = ops[i][1];int k = dp[right] - dp[left - 1];printf("%d\n", k + cont);
}