关于线段树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 ...
随机推荐
- 【ABAP系列】SAP ABAP同时显示多个ALV的方法
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[ABAP系列]SAP ABAP同时显示多个AL ...
- 安全运维 - Windows系统攻击回溯
Windows应急事件 病毒.木马.蠕虫 Web服务器入侵事件或第三方服务入侵事件 系统入侵事件 网络攻击事件(DDOS.ARP.DNS劫持等) 通用排查思路 获知异常事件基本情况 发现主机异常现象的 ...
- mysql 多表查询 以及 concat 、concat_ws和 group_concat
left join(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录right join(右联接) 返回包括右表中的所有记录和左表中联结字段相等的记录inner join(等值连接) 只返 ...
- Redis主从同步、哨兵、集群
什么是主从同步(复制) 主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器.前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点 ...
- Ftp客户端(上传文件)
#coding=utf-8 import os import socket import hashlib import json # client = socket.socket() #申明socke ...
- 25.conda 下载安装与运用
转载:https://www.cnblogs.com/gandoufu/p/9748841.html https://blog.csdn.net/tuzixini/article/details/81 ...
- 项目常见bug
Invalid prop: type check failed for prop "disabled". Expected Boolean, got String with val ...
- 在eclipse里搜索maven项目需要的dependency
eclipse直接就可以通过下载同步仓库索引,直接关键字查询需要的dependency. 前提是你已经在你的eclipse上配好了maven正确的环境. 1. 设置在开启eclipse时下载同步仓库索 ...
- python时间测量
使用自定义装饰器测量时间 def test_time(func): def inner(*args, **kw): t1 = datetime.datetime.now() print('开始时间:' ...
- linux下的mongodb的备份与恢复
mongodb的备份有两种方式: 1.直接拷贝数据目录下的一切文件 2.使用mongodump方式 3.主从复制:http://www.cnblogs.com/huangxincheng/archiv ...