关于线段树or 树状树状 在二维平面搞事情!Orz
第一式:https://ac.nowcoder.com/acm/contest/143/I
题意:
有 n 个点,一个点集 S 是好的,当且仅当对于他的每个子集 T,存在一个右边无限长的矩形,使得这个矩形包含了 T,但是和 S-T 没有交
求这 n 个点里有几个好的点集
1<=n<=10^5

1):当只选取1个点时,我们可以发现,任何一个点都满足题意。
2):当我们选取2个点时,我们可以发现如果要满足一个无限向右的矩形只框住一个点,当且仅当两个点的纵坐标不相同。因此,对于选2个点的总的方案数等于C(n,2)-C(纵坐标相同的个数,2)
3):当我们选3个点的时候(假设三个点为a,b,c),我们可以发现,当我们选取{a,b}作为子集,倘如第三个点c在{a,b}的右边,则我们发现由{a,b}组成的矩形一定包含{c},故不成立。因此{c}必定在{a,b}的左边,即当且仅当三个点的能够构成一个'<'号的形式才能够符合题意。
4):当选4个点及以上时,我们发现不管怎么样摆,均不可能出现3)的情况,故4个点以上的点是不合理的。
因此现在我们只需要处理的就是3)中的情况。对于3)的情况。我们只要求出在第i个点之前,有多少个点的x坐标比当前点大(记位below),再求出在第i个点之后有多少个点的x坐标比当前点大(记位above),那么对于第i个点而言,该点的方案数即为below*above了。
而对于below和above值的维护,我们需要先将y坐标进行离散化,然后将数组按照x坐标进行排序,然后用树状数组对区间进行维护即可。
现在问题的转化为在平面上有多少个不同的3对点集可以构成 "<" 的形状

现在观察此图可以发现:
对于包括点1的点集的情况无非就是 在点1上面的点与在点1下面的点来构成,那总的答案就一个组合的问题 cnt1*xcnt2;
所以我们就要只要在点1上面的点的个数与在点一下面点的个数(还要保证是在点1的右边)
所以很自然的想到对x排序,然后从x大开始便利(因为比当前点的 x小的点是没有价值的),用树状树状维护一个y上面的前缀和既可
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mod = ;
const int maxn=;
ll n;
struct no
{
int x,y;
}a[maxn];
int tree[maxn*],b[maxn];
ll box[maxn];
void add(int x , int c)
{
while(x<=n)
{
tree[x]+=c;
x+=(x&(-x));
}
}
int sum(int x)
{
int ret=;
while(x)
{
ret+=tree[x];
x-=(x&(-x));
}
return ret;
}
bool cmp(no a , no b)
{
return a.x>b.x;
}
int main()
{
scanf("%lld",&n);
for(int i= ; i<=n ; i++)
{
scanf("%d%d",&a[i].x,&a[i].y);
b[i]=a[i].y;
}
sort(b+,b++n);
for(int i= ; i<=n ; i++)
{
a[i].y=lower_bound(b+,b++n,a[i].y)-b;
box[a[i].y]++;
}
ll ans=n+n*(n-)/; ///统计一个点和两个点的情况
for(int i= ; i<=n ; i++)
ans-=box[i]*(box[i]-)/; ///减去两个点有相同的y
ans=(ans+mod)%mod;
///统计 "<" 的情况
int L=1;
sort(a+,a++n,cmp); for(int i= ; i<=n ; i++)
{
ll down=sum(a[i].y-);
ll up=sum(n)-sum(a[i].y);
ans=(ans+down*up)%mod;
if(a[i].x!=a[i+].x)
{
for(;L<=i ; L++)
add(a[L].y,);
}
}
printf("%lld\n",ans);
return ;
}
第二式:http://codeforces.com/contest/1191/problem/F
题意: 给出在二维平面上的点,问有多少个上面无限延长的矩阵是不同的 , 不同的定义为矩阵里面的点集是不同的
分析:这需要对x,y进行一个离散化的操作:
注意到其实从上往下一行一行扫过去,每次必须新增的元素才是新的集合,那很容易想到一个不重不漏的办法就是每次计算“以点p[i]为加进去的新点中的结束的集合”,那么假设一开始p[i]的左侧有cntl个点,那么显然有(cntl+1)条线在p[i]的左侧,而p[i]的右侧有cntr个点,也是(cntr+1)条线。
这个cntl显然就是query(1,p[i].x-1),而右侧则是query(p[i].x+1,p[i+1].x-1),因为不能包含同y的下一个点p[i+1],而其中,上面的点选法也会产生区别。
这个线段树的更新操作是赋值而不是加+1 , (因为如果同一条竖线上有多个点那其实排序去选择也只有一个价值而已)
那么每层加入一个正无穷也就是xn+1就可以了。

结合这图可以很容易看懂线段树的操作:1点的答案为(3*3) , 同时也可以理解为什么右侧是query(p[i].x+1,p[i+1].x-1),而不是说从当前点到最右面 , 想象一下,如果点1和点2右边的价值都是如此计算,那必然会存在重复计算。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll; int n;
struct Point {
int x, y;
bool operator<(const Point &p)const {
return y == p.y ? x<p.x: y>p.y;
//y从上到下,x从左到右
}
} p[]; int x[];
int y[]; ll sum; const int MAXM = ;
int st[(MAXM << ) + ]; inline void push_up(int o) {
st[o] = st[o << ] + st[o << | ];
} void build(int o, int l, int r) {
if(l == r) {
st[o] = ;
} else {
int m = (l + r) >> ;
build(o << , l, m);
build(o << | , m + , r);
push_up(o);
}
} void update(int o, int l, int r, int x, int v) {
if(l == r) {
//不是加,是赋值,同x的点是没有差别的
st[o] = v;
return;
} else {
int m = (l + r) >> ;
if(x <= m)
update(o << , l, m, x, v);
else if(x >= m + )
update(o << | , m + , r, x, v);
push_up(o);
}
} int query(int o, int l, int r, int a, int b) {
if(b < a)
return ;
else if(a <= l && r <= b) {
return st[o];
} else {
int m = (l + r) >> ;
int ans = ;
if(a <= m)
ans = query(o << , l, m, a, b);
if(b >= m + )
ans += query(o << | , m + , r, a, b);
return ans;
}
} int vx[], vxtop; int main() {
#ifdef Yinku
freopen("Yinku.in", "r", stdin);
//freopen("Yinku.out", "w", stdout);
#endif // Yinku
while(~scanf("%d", &n)) {
for(int i = ; i <= n; i++) {
scanf("%d%d", &p[i].x, &p[i].y);
x[i] = p[i].x;
y[i] = p[i].y;
}
sort(x + , x + + n);
int xn = unique(x + , x + + n) - (x + );
sort(y + , y + + n);
int yn = unique(y + , y + + n) - (y + );
for(int i = ; i <= n; i++) {
p[i].x = lower_bound(x + , x + + xn, p[i].x) - x;
p[i].y = lower_bound(y + , y + + yn, p[i].y) - y;
//从1开始分配新的坐标
//printf("(%d,%d)\n", p[i].x, p[i].y);
}
sort(p + , p + + n);
//扫描线
sum = ;
build(, , xn + );
int beg = , cur = ;
while(beg <= n) {
vxtop = ;
while(p[cur].y == p[beg].y) {
update(, , xn + , p[cur].x, );
vx[++vxtop] = p[cur].x;
/*
//点是不会重合的,那包含这个最左侧的点的都是全新集合
int cntl = query(1, 1, xn, 1, p[cur].x - 1);
//在这个点的左侧有cntl个x不同的点,那就有cntl+1个位置
//sum += (cntl + 1); X
//是以这个点为右侧边界的,所以右侧没得选 X
*/
//该层y中是以这个x点为右侧边界,但是两个x点之间的上层y也是可选的
cur++;
}
vx[++vxtop] = xn + ;
for(int i = ; i <= vxtop - ; i++) {
//该层最右端的新点为vx[i]的数量
int cntl = query(, , xn + , , vx[i] - );
///同层或者上层数量
int cntr = query(, , xn + , vx[i] + , vx[i + ] - );
///在vx[i] + 1, vx[i + 1] - 1 横坐标范围 上层的数量,
sum += 1ll * (cntl + ) * (cntr + );
// printf("sum=%lld vx[i]=%d cnt1=%d cnt2=%d\n",sum,vx[i],cntl,cntr,);
}
beg = cur; }
printf("%lld\n", sum);
}
}
第三式:http://codeforces.com/contest/1194/problem/E
题意:
给N条线段 , 线段只有垂直或者水平 , 问有多少个不同的4个线段集合构成(
h1和h2是水平;
v1和v2是垂直的;
h1段与v1段相交;
h2段与v1段相交;
h1段与v2段相交;
h2段与v2段相交。
) 也就是一个#差不多:
分析:在代码里面有注释了, 觉得看了代码理解起来不难
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+;
int c[N];
int n;
int lowbit(int x)
{
return x&(-x);
}
void add(int i , int x)
{
while(i<N)
{
c[i]+=x;
i+=lowbit(i);
}
}
int sum(int i)
{
int ans=;
while(i)
{
ans+=c[i];
i-=lowbit(i);
}
return ans;
}
struct svno
{
int y1,y2,x;
bool operator <(const svno& Q) const
{
return x<Q.x;
}
};
struct hvno
{
int x1,x2,y;
bool operator <(const hvno& Q) const
{
return x2<Q.x2;
}
};
vector<svno> SV;
vector<hvno>HV;
int main()
{
while(~scanf("%d",&n))
{
memset(c,,sizeof(c));
HV.clear();
SV.clear();
ll ans=;
for(int i= ; i<n ; i++)
{
int x1,x2,y1,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
x1+= , x2+= , y1+= , y2+=;
if(x1==x2)
{
if(y1>y2) swap(y1,y2);
SV.push_back({y1,y2,x1});
}
else
{
if(x1>x2) swap(x1,x2);
HV.push_back({x1,x2,y1});
}
sort(SV.begin(),SV.end());
sort(HV.begin(),HV.end());
}
vector<int>temp;
int lensv=SV.size();
int lenhv=HV.size();
///枚举第一条竖线
for(int i= ; i<lensv ; i++)
{
///加入符合的横线
temp.clear();
for(int j= ; j<lenhv ; j++)
{
if(HV[j].x1<=SV[i].x&&HV[j].x2>=SV[i].x&&HV[j].y>=SV[i].y1&&HV[j].y<=SV[i].y2)
{
temp.push_back(j);
add(HV[j].y,); }
}
///枚举第二条竖线
int lent=temp.size();
int k=;
for(int j=i+ ; j<lensv ; j++)
{
///删除枚举的第二条竖线时不满足条件的横线
///其实只要考虑横线在枚举的竖线的左边的情况
for( ; k<lent ; k++)
{
int k_id=temp[k];
if(HV[k_id].x2<SV[j].x)
{
add(HV[k_id].y,-);
}
else break;
}
///计算价值
ll cnt=sum(SV[j].y2)-sum(SV[j].y1-);
ans+=cnt*(cnt-)/;
// cout<<cnt<<endl;
}
///删除剩余的横线
for(;k<lent;k++)
{
add(HV[temp[k]].y,-);
}
}printf("%lld\n",ans);
}
}
总的来说这种题目不难 , 有时间还是可以想到一些东西的 。Orz
关于线段树or 树状树状 在二维平面搞事情!Orz的更多相关文章
- HDU 5877 dfs+ 线段树(或+树状树组)
1.HDU 5877 Weak Pair 2.总结:有多种做法,这里写了dfs+线段树(或+树状树组),还可用主席树或平衡树,但还不会这两个 3.思路:利用dfs遍历子节点,同时对于每个子节点au, ...
- dfs序+主席树 或者 树链剖分+主席树(没写) 或者 线段树套线段树 或者 线段树套splay 或者 线段树套树状数组 bzoj 4448
4448: [Scoi2015]情报传递 Time Limit: 20 Sec Memory Limit: 256 MBSubmit: 588 Solved: 308[Submit][Status ...
- P5666-[CSP-S2019]树的重心【树状数组】
正题 题目链接:https://www.luogu.com.cn/problem/P5666 题目大意 给出\(n\)个点的一棵树,对于每条边割掉后两棵树重心编号和. \(1\leq T\leq 5, ...
- LuoguP3834 【模板】可持久化线段树 1(主席树)|| 离散化
题目:[模板]可持久化线段树 1(主席树) 不知道说啥. #include<cstdio> #include<cstring> #include<iostream> ...
- 洛谷P3834 [模板]可持久化线段树1(主席树) [主席树]
题目传送门 可持久化线段树1(主席树) 题目背景 这是个非常经典的主席树入门题——静态区间第K小 数据已经过加强,请使用主席树.同时请注意常数优化 题目描述 如题,给定N个正整数构成的序列,将对于指定 ...
- luogu3703 [SDOI2017]树点涂色(线段树+树链剖分+动态树)
link 你谷的第一篇题解没用写LCT,然后没观察懂,但是自己YY了一种不用LCT的做法 我们考虑对于每个点,维护一个fa,代表以1为根时候这个点的父亲 再维护一个bel,由于一个颜色相同的段一定是一 ...
- 【洛谷P3834】(模板)可持久化线段树 1(主席树)
[模板]可持久化线段树 1(主席树) https://www.luogu.org/problemnew/show/P3834 主席树支持历史查询,空间复杂度为O(nlogn),需要动态开点 本题用一个 ...
- 【bzoj1036】树的统计[ZJOI2008]树链剖分+线段树
题目传送门:1036: [ZJOI2008]树的统计Count 这道题是我第一次打树剖的板子,虽然代码有点长,但是“打起来很爽”,而且整道题只花了不到1.5h+,还是一遍过样例!一次提交AC!(难道前 ...
- 洛谷$P2572\ [SCOI2010]$ 序列操作 线段树/珂朵莉树
正解:线段树/珂朵莉树 解题报告: 传送门$w$ 本来是想写线段树的,,,然后神仙$tt$跟我港可以用珂朵莉所以决定顺便学下珂朵莉趴$QwQ$ 还是先写线段树做法$QwQ$? 操作一二三四都很$eas ...
随机推荐
- Linux(Ubuntu)常用命令(三)
查看时间 cal :显示当前日期. cal :显示全年日历./ cal -y 显示当年日历. date :显示当前时间. 这几个一般不会用到了解即可. 查看进程信息 ps :显示当前进程. - ...
- js中ajax请求返回的数据处理成数组后,局部变量赋值给全局变量后,为空
第二步是想把ss的值扔给res_r,两个数组直接相等即可,可谁想到,取出来的值是空. 如图取出来的值是空. 我一脸懵逼,调试了些许时间,最后把ss遍历一下,在重新push进res_r 再来看效果,已经 ...
- python列表-使用
一.列表用于循环 1.for循环 2. in 和 not in 3.多重赋值
- DB2创建库 数据恢复
例:数据库:PRODB2用户 :DB2ADMIN/DB2ADMIN备份库路径:D:/bank 一.恢复数据库1.启动数据库运行->db2cmd->db2Db2=>start db m ...
- HDU.6186.CSCource.(前缀和数组和后缀和数组)
明天后天是南昌赛了嘤嘤嘤,这几天就先不更新每日题目了,以后补题嘤嘤嘤. 今天和队友做了一套2017年广西邀请赛,5个题还是有点膨胀...... 好了,先来说一下有意思的题目吧...... CS Cou ...
- 贪心(change)
http://codeforces.com/gym/100989/problem/H After the data structures exam, students lined up in the ...
- Redis的持久化存储
Redis的持久化 Redis 是一种内存型数据库,一旦服务器进程退出,数据库的数据就会丢失,为了解决这个问题, Redis 提供了两种持久化的方案,将内存中的数据保存到磁盘中,避免数据的丢失. RD ...
- (五:NIO系列) Reactor模式
出处:Reactor模式 本文目录 1. 为什么是Reactor模式 2. Reactor模式简介 3. 多线程IO的致命缺陷 4. 单线程Reactor模型 4.1. 什么是单线程Reactor呢? ...
- 靶形数独 (dfs+预处理+状态压缩)
#2591. 「NOIP2009」靶形数独 [题目描述] 小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他们想用数独来一比高低.但普通的数独对他们来说都过于简单了,于是他们 ...
- Python学习第四十一天函数装饰器传参数的用法
在不改变函数的结构的基础,我们给函数加新的功能,用是函数装饰器,如果要给函数传递参数,那么应该怎么做呢 @timerdef test2(name,age): time.sleep(3) print(' ...