标签:rt min 套路 所有 tree mid int 区间 mx
改题改题改题!!!做完题一定要好好改真的会继续两次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
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。
