NFLSHC集训Day4

· · 算法·理论

安排

早八上课下午答疑,中午睡一下,我又把手表戴上了

今日大纲

进阶线段树的应用

从今天开始就是疯狂的刷题时间了,题目也难了不少,来看看:

P1

这题难想在如何处理前缀,题目中的 L 和 R 我们可以分别用 0 和 1来代替,一个很自然的想法是用线段树维护答案区间的左右端点,但是问题在于直接这么写不仅麻烦还会TLE,有没有什么巧思呢?

先介绍一下代码里的数组:

$rans[i]$ :线段树中第 $i$ 个节点所维护的区间的右端点; $ans[i]$ :线段树中第 $i$ 个节点所维护的区间的最长符合条件的区间长度; 这样可以 $ O(1) $ 完成信息的维护操作; 重点来考虑一下 $pushup$ 的操作,简单来说分成两块考虑:左边和右边可以拼;左边和右边不能拼,先看看我的 $pushup$ : ```cpp void pushup(int p, int l, int r) { int cur = 0, mid = (l + r) / 2; cur = max(ans[p * 2], ans[p * 2 + 1]); if(a[mid] != a[mid + 1]) cur = max(cur, rans[p * 2] + lans[p * 2 + 1]); ans[p] = cur; lans[p] = lans[p * 2]; if(ans[p * 2] == mid - l + 1 && a[mid] != a[mid + 1]) lans[p] = mid - l + 1 + lans[p * 2 + 1]; rans[p] = rans[p * 2 + 1]; if(ans[p * 2 + 1] == r - mid && a[mid] != a[mid + 1]) rans[p] = r - mid + rans[p * 2]; } ``` 其余部分和板子差不多,看看我写的: ```cpp #include <bits/stdc++.h> using namespace std; const int N = 2e5 + 10; int ans[4 * N], lans[4 * N], rans[4 * N]; bool a[N]; void pushup(int p, int l, int r) { int cur = 0, mid = (l + r) / 2; cur = max(ans[p * 2], ans[p * 2 + 1]); if(a[mid] != a[mid + 1]) cur = max(cur, rans[p * 2] + lans[p * 2 + 1]); ans[p] = cur; lans[p] = lans[p * 2]; if(ans[p * 2] == mid - l + 1 && a[mid] != a[mid + 1]) lans[p] = mid - l + 1 + lans[p * 2 + 1]; rans[p] = rans[p * 2 + 1]; if(ans[p * 2 + 1] == r - mid && a[mid] != a[mid + 1]) rans[p] = r - mid + rans[p * 2]; } void build(int p, int l, int r) { if(l == r) { ans[p] = lans[p] = rans[p] = 1; return ; } int mid = (l + r) / 2; build(p * 2, l, mid); build(p * 2 + 1, mid + 1, r); ans[p] = lans[p] = rans[p] = 1; } void upd(int p, int l, int r, int x) { if(l == r) return ; int mid = (l + r) / 2; if(x <= mid) upd(p * 2, l, mid, x); else upd(p * 2 + 1, mid + 1, r, x); pushup(p, l, r); } int main() { int n, m, x; cin >> n >> m; build(1, 1, n); while(m--) { cin >> x; a[x] = !a[x]; upd(1, 1, n, x); cout << ans[1] <<endl; } return 0; } ``` [P2](https://www.luogu.com.cn/problem/P5648) 这题被放在线段树的题单里,我一开始按照线段树的思路想,但我意识到一点:线段树最大的优点是啥?修改啊!但是题目和我写的线段树里怎么一点修改都不用呢?说明很重要的一点:用线段树完成这题太大材小用了!我仔细审视才发现一点,助手小李给个图例: ![](https://cdn.luogu.com.cn/upload/image_hosting/3mn170bk.png) 对啊!你不觉得像倍增吗?为什么不试试用倍增做呢?其中的异或操作顺题模拟就行,看看我写的: ```cpp #include <bits/stdc++.h> #define int long long using namespace std; const int N = 500100; int f[N][20], a[N]; int d[N][20], q[N]; signed main() { int n, m; int u, v; cin >> n >> m; for(int i = 1; i <= n; i++) { cin >> a[i]; } int top = 0; for(int i = 1; i <= n; i++) { while(top > 0 && a[q[top]] < a[i]) { d[q[top]][0] = i; f[q[top]][0] = a[q[top]] * (i - q[top]); top--; } q[++top] = i; } while(top) { d[q[top]][0] = n + 1; f[q[top]][0] = a[q[top]] * (n - q[top] + 1); top--; } d[n + 1][0] = n + 1; f[n + 1][0] = 0; for(int j = 1; j < 19; j++) { for(int i = 1; i <= n + 1; i++) { d[i][j] = d[d[i][j - 1]][j - 1]; f[i][j] = f[i][j - 1] + f[d[i][j - 1]][j - 1]; } } int ans = 0; while(m--) { cin >> u >> v; int l = 1LL + (u ^ ans) % (long long)n, q = 1LL + (v ^ (ans + 1)) % ((long long)n - l + 1), r = l + q - 1; int p = l; ans = 0; for(int i = 18; ~i; i--) { if (d[p][i]<=r) { ans += f[p][i]; p = d[p][i]; } } if(p <= r) ans += a[p] * (r - p + 1); cout << ans <<endl; } return 0; } ``` 写完我得到了重要的结果,不是所有打了线段树tag的题目都只能拿线段树写,具体问题具体分析 [P3](https://www.luogu.com.cn/problem/P5522) 这题真的是变态到一种境界,其变态之处在于要不断地优化自己的代码调整,否则老是会TLE,看看我的血泪史: 1. 发现过不去,把 define int long long 改掉了 2. 发现还是过不去,将所有的 *2 换成了位运算形式 3. 发现还是过不去,使用快速读入 4. 发现还是过不去,将 $cnt$ 数组改为 $bool$ 类型,将加运算改为或运算,终于过了 看看我的辛酸代码: ```cpp #include <bits/stdc++.h> #define ll long long using namespace std; const int N = 101000; int n; struct node { bool cnt0[30], cnt1[30]; }cnt[N << 2]; char s[30]; int get() { int x = 0; char ch = getchar(); while (!(ch>='0' && ch<='9')) { ch = getchar(); } while (ch>='0' && ch<='9') { x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); } return x; } node pushup(node x, node y) { node res; for(int i = 0; i < n; i++) { res.cnt0[i] = x.cnt0[i] | y.cnt0[i]; res.cnt1[i] = x.cnt1[i] | y.cnt1[i]; } return res; } void upd(int p, int l, int r, int x) { if(l == r) { memset(cnt[p].cnt0, 0, sizeof(cnt[p].cnt0)); memset(cnt[p].cnt1, 0, sizeof(cnt[p].cnt1)); for(int i = 0; i < n; i++) { if(s[i] == '0') cnt[p].cnt0[i] = 1; if(s[i] == '1') cnt[p].cnt1[i] = 1; } return ; } int mid = l + r >> 1; if(x <= mid) upd(p << 1, l, mid, x); else upd(p << 1 | 1, mid + 1, r, x); cnt[p] = pushup(cnt[p << 1], cnt[p << 1 | 1]); } node qry(int p, int l, int r, int x, int y) { if(l == x && y == r) return cnt[p]; int mid = l + r >> 1; if(y <= mid) return qry(p << 1, l, mid, x, y); if(x > mid) return qry(p << 1 | 1, mid + 1, r, x, y); return pushup(qry(p << 1, l, mid, x, mid), qry(p << 1 | 1, mid + 1, r, mid + 1, y)); } int getans(node cur) { int p = 1; for(int i = 0; i < n; i++) { if(cur.cnt0[i] && cur.cnt1[i]) return 0; if(cur.cnt0[i] == 0 && cur.cnt1[i] == 0) p = p << 1; } return p; } int main() { int m, q, op, l, r, x; n = get(); m = get(); q = get(); for(int i = 1; i <= m; i++) { scanf("%s", s); upd(1, 1, m, i); } int ans = 0; while(q--) { op = get(); if(op == 0) { l = get(); r = get(); ans ^= getans(qry(1, 1, m, l, r)); } else { x = get(); scanf("%s", s); upd(1, 1, m, x); } } cout << ans; return 0; } ```