所有区间问题的套路
作者:互联网
改题改题改题!!!做完题一定要好好改真的会继续两次NoNoNo继续n次遇见它!!!
C. 完美子图
线段树做法:考虑把一个区间向右拓展,新增加的一个位置对以前所有处理过的区间的影响,当然对自己这一个长度的影响是1,第一层循环枚举R,新加入的数能对过去的区间会造成影响只有两种情况:加入的新数成为了某段以i为右边界的区间的最大值或最小值,这个可以用单调栈来维护。
把图转成二维的之后问题就变成了max-min=r-l,但因为这是一个排列(代表没有重复),对于所有区间来说max-min>=r-l一定成立,所以合法的区间就是l+max-min的最小值=r,线段树上维护了l+max-min的最小值以及满足这个最小值的区间个数,建树的时候把 l 先放进去,长度只有1的时候max-min=0,正好是初始状态,更新时,(以最大值为例)新的最大值是a[i],被他影响到的上一个最大值是a[mx[t_mx]],虽然还可能有更多的被影响,但是要在每次出栈是更新因为“过去的最大值”变化了,而最大值的增量就是a[i]-a[mx[t_mx]],因为lazy放的是对最小值的更新所以当然不用*区间长度。
因为对于一个区间来说,max-min=r-l是它的最小情况,所以左右子树向上合并时,min相同才可能同时合法,min大的那个绝对不合法(已有比它小的它就不可能最小了),同样query的时候也不能把左右直接相加,分类讨论一下就好了。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 5e4 + 2; const ll mod = 998244353; const int INF = 2147483647; int n, a[maxn], mx[maxn], mi[maxn], t_mx, t_mi; ll ans; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } struct node { int ls, rs, cnt, min, lazy; }tree[maxn<<2]; int tot; #define lson tree[rt].ls #define rson tree[rt].rs void pushup(int rt) { if(tree[lson].min == tree[rson].min) { tree[rt].min = tree[lson].min; tree[rt].cnt = tree[lson].cnt+tree[rson].cnt; return; } if(tree[lson].min > tree[rson].min) { tree[rt].min = tree[rson].min; tree[rt].cnt = tree[rson].cnt; return; } tree[rt].min = tree[lson].min; tree[rt].cnt = tree[lson].cnt; } void pushdown(int rt) { if(tree[rt].lazy) { int lz = tree[rt].lazy; tree[rt].lazy = 0; tree[lson].lazy += lz; tree[rson].lazy += lz; tree[lson].min += lz; tree[rson].min += lz; } } int build(int rt, int l, int r) { if(!rt) rt = ++tot; if(l == r) { tree[rt].cnt = 1; tree[rt].min = l; return rt; } int mid = (l + r) >> 1; tree[rt].ls = build(lson, l, mid); tree[rt].rs = build(rson, mid+1, r); pushup(rt); return rt; } void update(int rt, int l, int r, int L, int R, int val) { if(L <= l && r <= R) { tree[rt].lazy += val; tree[rt].min += val; return; } pushdown(rt); int mid = (l + r) >> 1; if(L <= mid) update(lson, l, mid, L, R, val); if(R > mid) update(rson, mid+1, r, L, R, val); pushup(rt); } struct node2 { int id, v; node2(){} node2(int x, int y) {id = x, v = y;} }; node2 query(int rt, int l, int r, int L, int R) { if(L <= l && r <= R) { return node2(tree[rt].cnt, tree[rt].min); } pushdown(rt); int mid = (l + r) >> 1; node2 left, right; left.v = right.v = 1e9; if(L <= mid) left = query(lson, l, mid, L, R); if(R > mid) right = query(rson, mid+1, r, L, R); if(left.v < right.v) return left; if(right.v < left.v) return right; left.id += right.id; return left; } int main() { n = read(); for(int i=1; i<=n; i++) { int x = read(), y = read(); a[x] = y; } tot = 1; int root = build(1, 1, n); for(int i=1; i<=n; i++) { while(t_mx > 0 && a[mx[t_mx]] < a[i]) { update(root, 1, n, mx[t_mx-1]+1, mx[t_mx], a[i]-a[mx[t_mx]]); t_mx--; } mx[++t_mx] = i; while(t_mi > 0 && a[mi[t_mi]] > a[i]) { update(root, 1, n, mi[t_mi-1]+1, mi[t_mi], a[mi[t_mi]]-a[i]); t_mi--; } mi[++t_mi] = i; node2 cat = query(1, 1, n, 1, i); if(cat.v == i) ans += (ll)cat.id; } printf("%lld\n", ans); return 0; }View Code
——感谢caorong
还可以用cdq分治:在合并的时候还是把区间想像成左右两部分,分类讨论一下:两个最值都在左; 都在右; min在左,max在右;min在右,max在左4种情况,也就是每次考虑跨过中点的区间答案,细节注释在了代码里。
#include <bits/stdc++.h> using namespace std; typedef unsigned long long ll; const int maxn = 50003; const ll mod = 998244353; const int INF = 0x7ffffff; int a[maxn], Min[maxn], Max[maxn], cnt[maxn<<1]; int ans, n; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } void solve(int l, int r) { if(l == r) { ans++; return; } int mid = (l + r) >> 1; solve(l, mid); solve(mid+1, r); Min[mid] = Max[mid] = a[mid]; Min[mid+1] = Max[mid+1] = a[mid+1]; for(int i=mid-1; i>=l; i--) { Min[i] = min(Min[i+1], a[i]); Max[i] = max(Max[i+1], a[i]); } for(int i=mid+2; i<=r; i++) { Min[i] = min(Min[i-1], a[i]); Max[i] = max(Max[i-1], a[i]); } //两个最值都在左区间, for(int i=mid; i>=l; i--) { int j=i+Max[i]-Min[i];//如果通过最值找到的右端点合法 if(j<=r && j>mid && Max[i]>Max[j] && Min[i]<Min[j]) ans++; } for(int j=mid+1; j<=r; j++) { int i=j-Max[j]+Min[j]; if(i>=l && i<=mid && Max[i]<Max[j] && Min[i]>Min[j]) ans++; } int j = mid+1, k = mid+1; for(int i=mid; i>=l; i--)//左小右大 { while(j<=r && Min[j]>Min[i])//j满足左小,j是mid+1到右边界的区间,而不是右端点 { cnt[Max[j]-j+n]++;//在干嘛?所以j是一个符合条件的区间,Max[j]-j==Min[i]-i j++; } //Max是单调的,越接近越可能会合法,和上面的Min不同,它反向了 //j不变,因为Min[i]是区间最小值,只有可能变得更小或不变,而变得更小就使得j的范围更大 while(k<j && Max[i]>Max[k])//为了避免重复,一定要满足右大,可是为什么要比j小?而且,k是右区间, //这是在清理重复的情况,把左大的删掉 { cnt[Max[k]-k+n]--; k++; } //同上,k不需要清空 ans += cnt[Min[i]-i+n];//为了满足区间长度的条件,上面存的只是可能合法的(左小右大的) } while(k<j) { cnt[Max[k]-k+n]--;//其实就是清空数组的意思,memset会TLE k++; } j = mid, k = mid; for(int i=mid+1; i<=r; i++) { while(j>=l && Min[i]<Min[j]) { cnt[Max[j]+j]++; j--; } while(k>j && Max[k]<Max[i]) { cnt[Max[k]+k]--; k--; } ans += cnt[Min[i]+i];//Max[i]-Min[j]==j-i,移项 } while(k>j) { cnt[Max[k]+k]--; k--; } } int main() { n = read(); for(int i=1; i<=n; i++) { int x = read(), y = read(); a[x] = y; } solve(1, n); printf("%d", ans); return 0; }View Code
B. 任意模数快速插值
未完待续……
标签:rt,min,套路,所有,tree,mid,int,区间,mx 来源: https://www.cnblogs.com/Catherine2006/p/16536376.html