闲话 22.9.28
闲话
今日有多少人切猪国杀呢?我不知道
但我知道的是我反正没敢碰
在这么一个大好的没有模拟赛的时间
为啥要切猪国杀呢
有闲话的构思
但是不觉得能在今天写完
于是今天先瞎扯一点
没准几天之后你们就能看到我想的是啥了
但慢慢等吧(咕咕咕
放假遥遥无期
我推测一下我放假回家能看到cod20发售
我推测一下我放假回家能看到gta6发售
我推测一下我放假回家能听到饭的新歌
我推测一下我放假回家能看到游戏人生12出版
我推测一下我放假回家能看到游戏人生2
我推测一下我放假回家能看到魔禁完结
完了越来越遥遥无期了
upd at 23.4.3
cod20 是 2024 年
gta6 是 2024~25 年
饭的新歌已经出了 是 dinner bell
游戏人生 12 出版了
游戏人生 2 仍然不太可能
魔禁……6
調子はどうですか
空は自由に飛べましたか
すごい魔法が出せましたか
全部夢の中限定品さ
生きてる事ってなんだろな
生きてる人は怖いからな
触れないものを信じるのは
馬鹿のすることと聞きました
……
分治
一类用于解决连续段问题及其进阶问题的小常数做法。由于实现方式的性质,其时间经常是线段树做法的三分之一甚至更小。并且其经常值域无关,这也就给蓄意对std进行卡常然后卡掉线段树提供了借口
而且写起来确实简单
给定一个长为 \(n\) 的序列 \(a\)。定义 \(max(l,r)\) 为 \(\max_{i=l}^r\{a_i\}\),\(min(l,r)\) 同理。在模 \(10^9+7\) 意义下求下式的值:
\[\sum_{l=1}^n\sum_{r=l}^nmax(l,r)\times min(l,r) \]\(n \le 5\times 10^5, a_i < 10^9 + 7\).
你当然可以线段树叶子表示左端点,再维护两个单调栈做扫描,每次更新该点作为最大最小值的区间,每个节点在 \(\max\) 或 \(\min\) 相同时进行计算,并维护一个子区间 \(\max\) 和 \(\min\) 的和。太naive不展开。
我们现在考虑分治。
分治需要求解落在 \([l,r]\) 区间内的所有子区间。当 \(l=r\) 时有答案 \(a_l^2\),作为递归边界出现。
当 \(l\neq r\) 时可以将子区间按是否过当前区间中点 \(m = \lfloor \frac {l+r} 2\rfloor\) 分为两类。不过中点的可以作为子问题向两侧子区间递归,因此我们在递归函数内只需要处理过中点的区间即可。
我们可以将 \([l,m]\) 和 \([m+1,r]\) 分别预处理,讨论不同的最值取值方式。首先预处理 \(\forall pos \in [l,m],[pos,m]\) 和 \(\forall pos \in[m+1,r],[m+1,pos]\) 的最值 \(\min_{pos},\max_{pos}\),随后使用指针扫两侧区间。
接下来的叙述以扫左侧为例。
我们维护一个左侧单减指针 \(i\) 表示当前左侧扫到的位置,两个右侧单增指针 \(p_{\min},p_{max}\) 表示当 \(i\) 在特定位置时,比 \(\min_i\) 小的第一个值/比 \(\max_i\) 大的第一个值出现的位置。\(i\) 每次移动 \(1\),\(p\) 位置随 \(i\) 变化而变化。
考虑根据最大值和最小值的位置分别处理区间。
- 最值出现在中点同侧。
这时考虑这部分区间的最大值由 \(i\) 侧取得。因此这部分的数量为 \((\min(p_{\max}, p_{\min}) - 1) - (mid+1) + 1\),对答案的贡献为 \(\min_i\times \max_i\)。 - 最值出现在中点异侧。
钦定统计时最大值出现在左侧,因此记录最小值在两侧的前缀和 \(\text{sum}_ {pos}\)。只有 \(p_{min} < p_{max}\) 时该状态才存在,判一下就行。此时不同的右侧最小值之和为 \(\text{sum}_ {p_{max} - 1} - sum_{p_{min} - 1}\times [p_{min} \neq m+1]\),乘上 \(\max_i\) 就是答案。判后面那个的原因是在实现时 \(\text{sum}_ m\) 存的不是右侧的值,因此直接溢出的得数不是 \(0\)。
扫右侧的情况直接复制粘贴就好。
但是我们发现一件事,这样会算重。具体地说,我们在判断指针是否移动时需要写条件,比较 \(\min_p\) 和 \(\min_i\) (\(\max\))的关系。我们应当在两次计算中以不同的方式开闭区间,使他们互补。
具体地说,按照 \([l,r],(l,r)\) 的方式进行两次的计算是可行的,按照 \([l,r),(l,r]\) 的方式也行。但是如果按照 \([l,r],[l,r)\) 或者什么方式确定区间,则显然会重复计算。这部分的调整看代码实现。
记得分治。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
void get(T & x){ /* readIn */ }
#define rep(i,a,b) for (register int (i) = (a); (i) <= (b); ++(i))
#define pre(i,a,b) for (register int (i) = (a); (i) >= (b); --(i))
const int N = 5e5 + 10, mod = 1e9 + 7;
int n, a[N], ans;
int mx[N], mn[N], sum[N];
void dac (register int l, register int r) {
if (l == r) { ans = (ans + 1ll * a[l] * a[l]) % mod; return; }
int mid = l + r >> 1;
dac(l, mid), dac(mid+1, r);
sum[mid] = mx[mid] = mn[mid] = a[mid], sum[mid+1] = mx[mid+1] = mn[mid+1] = a[mid+1];
rep(i,mid+2,r) mn[i] = min(mn[i-1], a[i]), mx[i] = max(mx[i-1], a[i]), sum[i] = (sum[i-1] + mn[i]) % mod;
pre(i,mid-1,l) mn[i] = min(mn[i+1], a[i]), mx[i] = max(mx[i+1], a[i]), sum[i] = (sum[i+1] + mn[i]) % mod;
for (register int i = mid, p1 = mid+1, p2 = mid+1; i >= l; -- i) {
while (p1 <= r and mn[p1] > mn[i]) p1++;
while (p2 <= r and mx[p2] <= mx[i]) p2++; // (l,r], [l,r) 型的,如果想写 [l,r],(l,r) 的话可以在这里写 > < 在下面写 >= <=
ans = (ans + 1ll * mn[i] * mx[i] % mod * (min(p1, p2) - mid - 1)) % mod;
if (p1 < p2 and p2 > mid+1) ans = (ans + 1ll * mx[i] * (sum[p2 - 1] + (p1 != mid+1) * (mod - sum[p1 - 1]))) % mod;
}
for (register int i = mid+1, p1 = mid, p2 = mid; i <= r; ++ i) {
while (p1 >= l and mn[p1] >= mn[i]) p1--;
while (p2 >= l and mx[p2] < mx[i]) p2--;
ans = (ans + 1ll * mn[i] * mx[i] % mod * (mid - max(p1, p2))) % mod;
if (p1 > p2 and p2 < mid) ans = (ans + 1ll * mx[i] * (sum[p2 + 1] + (p1 != mid) * (mod - sum[p1 + 1]))) % mod;
}
}
signed main() {
get(n);
rep(i,1,n) get(a[i]);
dac(1, n);
cout << ans << endl;
return 0;
}
给定一个长为 \(n\) 的序列 \(a\)。求区间最大值与最小值的
popcount
值相同的区间个数。\(n \le 5\times 10^5, a_i \le 10^{18}\).
这个东西线段树需要做 \(\log(\max\{a_i\})\) 次。先用单调栈跑出关于每个popcount
的操作离线掉,然后再分别做。更改的总次数是 \(O(n)\) 的,因为每个位置只会造成一次修改。查询的总次数是 \(O(n \log n)\) 的,但是单次查询 \(O(1)\) 所以总复杂度还是 \(O(n \log n)\) 的。但是常数大的要死,那个 \(log\) 里面还有 \(\max\{a_i\}\)。于是考虑分治。
沿用上面的思路。首先套路地把前后缀最值处理出来。套路
然后分讨。仍然讨论 \(i\) 扫左侧的情况。
- 最值均出现在左侧。这时若 \(\text{popcount}(\max_i) = \text{popcount}(\min_i)\) 则有贡献。贡献值如上,仍为 \(\min(p_{\max}, p_{\min}) - mid-1\)。
- 钦定最大值出现在左侧。于是我们统计右侧每个位置 \(\min\) 前缀的
popcount
,进行一个差分就是答案。
思路较显然。
值得注意的是,分治在该题的时间为线段树的六分之一至七分之一。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
template <typename T> inline void get(T & x){ /* readIn */}
#define rep(i,a,b) for (register int (i) = (a); (i) <= (b); ++(i))
#define pre(i,a,b) for (register int (i) = (a); (i) >= (b); --(i))
const int N = 1e6 + 10; typedef long long ll;
#define pctl(u) ( __builtin_popcountll(u) )
int n, c[N], buk[64];
ll a[N], ans;
int mx[N], mn[N], buk1[64], buk2[64];
void dac (int l, int r) {
if (l == r) { ans++; return; }
int mid = l + r >> 1;
dac(l, mid), dac(mid+1, r);
mx[mid] = mn[mid] = mid, mx[mid+1] = mn[mid+1] = mid+1;
rep(i, mid+2, r) mx[i] = a[i] > a[mx[i-1]] ? i : mx[i-1], mn[i] = a[i] < a[mn[i-1]] ? i : mn[i-1];
pre(i, mid-1, l) mx[i] = a[i] > a[mx[i+1]] ? i : mx[i+1], mn[i] = a[i] < a[mn[i+1]] ? i : mn[i+1];
memset(buk1, 0, sizeof buk1), memset(buk2, 0, sizeof buk2);
for (int i = mid+1, p1 = mid, p2 = mid; i <= r; ++i) {
while (p1 >= l and a[mx[p1]] < a[mx[i]])
buk1[c[mn[p1--]]] ++ ;
while (p2 >= l and a[mn[p2]] >= a[mn[i]])
buk2[c[mn[p2--]]] ++ ;
if (c[mx[i]] == c[mn[i]]) ans += mid - max(p1, p2);
if (p1 < p2) ans += buk1[c[mx[i]]] - buk2[c[mx[i]]];
}
memset(buk1, 0, sizeof buk1), memset(buk2, 0, sizeof buk2);
for (int i = mid, p1 = mid+1, p2 = mid+1; i >= l; --i) {
while (p1 <= r and a[mx[i]] >= a[mx[p1]])
buk1[c[mn[p1++]]] ++;
while (p2 <= r and a[mn[i]] < a[mn[p2]])
buk2[c[mn[p2++]]] ++;
if (c[mx[i]] == c[mn[i]]) ans += min(p1, p2) - mid - 1;
if (p2 < p1) ans += buk1[c[mx[i]]] - buk2[c[mx[i]]];
}
}
signed main() {
get(n);
rep(i,1,n) get(a[i]), c[i] = pctl(a[i]);
dac(1, n);
cout << ans << endl;
return 0;
}
以下是博客签名,与正文无关。
请按如下方式引用此页:
本文作者 joke3579,原文链接:https://www.cnblogs.com/joke3579/p/chitchat220928.html。
遵循 CC BY-NC-SA 4.0 协议。
请读者尽量不要在评论区发布与博客内文完全无关的评论,视情况可能删除。