From e9d3c0cc91dddcb4aa19498bde9b954e401845ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Mon, 18 Dec 2023 10:40:07 +0800 Subject: [PATCH] 'commit' --- TangDou/Topic/PrefixAndSuffix/P1083.cpp | 9 ++- .../Topic/【前缀和与差分】题单.md | 65 +++++++++++++------ 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/TangDou/Topic/PrefixAndSuffix/P1083.cpp b/TangDou/Topic/PrefixAndSuffix/P1083.cpp index a964751..e4ee4cb 100644 --- a/TangDou/Topic/PrefixAndSuffix/P1083.cpp +++ b/TangDou/Topic/PrefixAndSuffix/P1083.cpp @@ -29,12 +29,17 @@ signed main() { for (int i = 1; i <= n; i++) cin >> r[i]; // 第i天可以租借的教室数量 for (int i = 1; i <= m; i++) cin >> d[i] >> s[i] >> t[i]; // 借多少个,从哪天借到哪天 - // ① 整体检查,如果所有订单都能通过,则输出0 + /* + ① 整体检查,如果所有订单都能通过,则输出0 + 先定性,再定量! + 我们先不思考二分不二分,先用check函数获取所有订单是不是可以通过,如果能通过,那二分个啥! + 如果不能过,再已知有问题订单的情况下,再去找出问题订单,这样思路是最清晰的! + 避免不管是否有问题订单,全都冗杂到一个二分检查办法中去,那样容易思路不清,造成丢分! + */ if (check(m)) { // 如果全部满足 cout << 0 << endl; // 输出0 exit(0); // 直接结束程序 } - /* ② 整体检查未通过,知道肯定有订单无法满足,此时,再想办法找出是哪个订单第一个出现不满足的情况 难道要一个个订单检查吗? diff --git a/TangDou/Topic/【前缀和与差分】题单.md b/TangDou/Topic/【前缀和与差分】题单.md index d4fc585..e6ba9ae 100644 --- a/TangDou/Topic/【前缀和与差分】题单.md +++ b/TangDou/Topic/【前缀和与差分】题单.md @@ -130,9 +130,9 @@ int main() { 再看到每个人借教室的时间可以看成一个区间,且该区间只会对其他在该区间要借教室的人产生影响,对于区间之外的借教室的人是不会产生影响的,于是又想到了差分。 -差分序列:(可用于区间增减)记录相邻两个量的变化量,所以当在一段区间$[l,r]$上增加$a$时,只需要在$l$处加$a$,在$r+1$处$-a$即可。 +**差分序列**:(可用于区间增减)记录相邻两个量的变化量,所以当在一段区间$[l,r]$上增加$d$时,只需要在$l$处加$d$,在$r+1$处$-d$ 即可。 -对于为什么可以二分:如果一个人无法被满足,则他后面的人全都不能被满足;如果一个人可以被满足,则他前面的人都可以被满足,这恰恰吻合了我们二分的性质。 +对于为什么可以二分:如果一个订单无法被满足,则它后面的订单全都不能被满足;如果一个订单可以被满足,则它前面的订单都可以被满足,这恰恰吻合了我们二分的性质。 ```cpp {.line-numbers} #include @@ -140,41 +140,68 @@ using namespace std; const int N = 1000010; #define int long long #define endl "\n" -int n, m; // 天数和订单的数量 -int r[N]; // 第i天学校有r[i]个教室可借用 -int d[N], s[N], t[N]; // 借的教室数目、从第s天借到t天 + +int n, m; // 天数和订单的数量 +int r[N]; // 第i天学校有r[i]个教室可借用 +int d[N], s[N], t[N]; // 借的教室数目、从第s天借到t天 + int b[N]; // 差分数组 -bool check(int x) { // 判断能不能通过x个人 +bool check(int mid) { // 判断能不能通过前mid个订单 memset(b, 0, sizeof b); // 每次判断都要先初始化差分数组 int sum = 0; // 记录需要借的教室数 - for (int i = 1; i <= x; i++) { - b[s[i]] += d[i]; // 因为只会对在s~l之间要借用教室的人产生影响,所以可以差分 - b[t[i] + 1] -= d[i]; // 差分,注意:是t[i]+1,因为要包含t[i]这个点 + // ① 构建差分数组 + for (int i = 1; i <= mid; i++) { // 枚举范围内[1~mid]所有订单 + b[s[i]] += d[i]; // 第i个订单,因为只会对在s~l之间要借用教室的人产生影响,所以可以差分 + b[t[i] + 1] -= d[i]; // 差分,注意:是t[i]+1,因为要包含t[i]这个点 } - for (int i = 1; i <= n; i++) { - sum += b[i]; // 因为cf是差分数组,所以sum就是在第i天的借教室的总数 - if (sum > r[i]) return false; // 不可行,如果要借的教室多于空的教室 + // ② 还原成原数组,sum=a[i],也就是第i天需要借的教室总数 + for (int i = 1; i <= n; i++) { // 枚举每一天 + sum += b[i]; // 因为b是差分数组,所以sum就是在第i天的借教室的总数 + if (sum > r[i]) return false; // 如果要借的教室多于空的教室,返回不可行 } - return true; // 可行 + return true; // 返回可行 } signed main() { - cin >> n >> m; - for (int i = 1; i <= n; i++) cin >> r[i]; - for (int i = 1; i <= m; i++) cin >> d[i] >> s[i] >> t[i]; + cin >> n >> m; // n:天数,m:订单数量 + for (int i = 1; i <= n; i++) cin >> r[i]; // 第i天可以租借的教室数量 + for (int i = 1; i <= m; i++) cin >> d[i] >> s[i] >> t[i]; // 借多少个,从哪天借到哪天 + + /* + ① 整体检查,如果所有订单都能通过,则输出0 + 先定性,再定量! + 我们先不思考二分不二分,先用check函数获取所有订单是不是可以通过,如果能通过,那二分个啥! + 如果不能过,再已知有问题订单的情况下,再去找出问题订单,这样思路是最清晰的! + 避免不管是否有问题订单,全都冗杂到一个二分检查办法中去,那样容易思路不清,造成丢分! + */ if (check(m)) { // 如果全部满足 cout << 0 << endl; // 输出0 exit(0); // 直接结束程序 } + /* + ② 整体检查未通过,知道肯定有订单无法满足,此时,再想办法找出是哪个订单第一个出现不满足的情况 + 难道要一个个订单检查吗? + 不断的增加订单,会使得差分数组变化,但我们只看差分数组是不能判断是否有问题发生的,需要把差分数组还原 + 为原数组后,才能进行检查,每输入一个订单,就还原一下原数组,那样就太慢了。 + 能不能少还原,还能判断准呢? + 答:可以的,因为这件事具有单调性!第x个订单加上后: + (1)如果1~x都符合条件,那证明前面的x-1个订单都是OK的, + (2)如果1~x不符合条件,那后续的追加更多订单后的检查也肯定会失败! + 所以,可以使用二分进行求解查找是哪个订单导致第一个失败情况出现。 + */ int l = 1, r = m; // 二分左右区间 while (l < r) { int mid = l + r >> 1; if (check(mid)) // 如果可行 - l = mid + 1; // 增多满足人数 + l = mid + 1; // 增多订单 else // 否则 - r = mid; // 减少满足人数 + r = mid; // 减少订单 } + + if (l == r) cout << 0 << end; + ; + cout << "-1" << endl - << l; // 输出-1和需要修改的人 + << l; // 输出-1和 第一个不符合条件的订单 } ```