题解-P3810
前置算法
- 树状数组求逆序对
- 归并排序求逆序对
解题之前,让我们来看一看弱化版本 \(\to\) 二维偏序
题意
给定两个长度为数组 \(a_1,a_2,\dots,a_n\),\(b_1,b_2,\dots,b_n\) 求对于每一个 \(i\),\(a_j\le a_i\) 且 \(b_j\le b_i\) 的 \(j\) 有多少个。
解法1
考虑用结构体把数组存起来,对 \(a\) 进行排序,再用一个值域树状数组维护 \(b\) 值即可。
还没完。由于可能出现 \(a_i=a_j\) 且 \(b_i=b_j\) 的情况,所以需要去重。
提到去重,就要在结构体里面多加一个 \(x\) 。 \(x_i\) 表示与 \(a_j=a_i,b_j=b_i\) 的 \(j\) 的个数,\(x_i\) 初始为 \(1\)
去重毒瘤的要死
解法2
还是用结构体存,对 \(a\) 进行排序+去重,后面考虑用归并排序来求
回想一下归并排序求逆序对,我们求的是 \(a_i\) 作为逆序对 \((j,i)\) 的 \(j\) 的总个数。
在这里,\(a\) 已经按大小排好了,所以我们只考虑对 \(b\) 值求逆序对就行了。
深刻注意解法2
三维偏序
第一步
和二维偏序一样,先按 \(a\) 值排好序,去重
第二步
为了简化题目,先考虑简易版本不存在 \(a_i=a_j\) 且 \(b_i=b_j\) 且 \(c_i=c_j\) 的情况,标准版本放在第三步。
进入到今天的正题: CDQ 讲解部分。
CDQ 分治,顾名思义,是一种分治。而分治,就需要把 \([l,r]\) 分为 \([l,mid]\) 和 \([mid+1,r]\) 。而对于我们要寻找的一个符合 \(a_j\le a_i,b_j\le b_i,c_j\le c_i\) 的点对 \((i,j)\) 必须符合以下三种情况之一:
- \(1\le i,j\le mid\gets\) 这在递归处理左半边时已经处理了
- \(mid\lt i,j\le n\gets\) 这在递归处理右半边时已经处理了
- \(1\le i,j\le n\gets\) 这是我们在递归之后需要处理的点对
按分治三部曲走,接下来就是合并左右区间并统计答案了了,这里按归并排序求逆序对的思路来。
由于 \(a,b\) 值都被我们处理好了,接下来就是毒瘤的 \(c\) 值,用树状数组维护。
在合并中,我们分两种情况:
- 此时的最小值在左,那么我们让树状数组的左边那个数的 \(c\) 值位置加一
- 此时的最小值在右,那么我们让答案加上树状数组从 \(1\) 到右边数的 \(c\) 值位置的和
如果搞不懂为什么左是加,右是查,建议重新看一看归并排序求逆序对。
注意:每一次使用完后树状数组要清空。如果单纯 memset
,会超时(因为是 \(O(n)\) ),清空应该对于每一个被存放在树状数组里的 \(c_i\) ,其在树状数组里面的值 \(-1\)
int tmp[maxn]; // 临时存放合并好的值的数组
void CDQ(int l,int r){
if(l == r) return;
int mid = l + r >> 1,p = l,q = mid + 1,len = 0;
CDQ(l,mid),CDQ(mid + 1,r); // 递归处理
while(p <= mid && q <= r){ // 合并子区间
if(a[p].b <= a[q].b) bit.update(a[p].c,1),tmp[++len] = a[p++]; // 选左边,此时更新树状数组
else a[q].ans += bit.query(a[q].c),tmp[++len] = a[q++]; // 选右边,此时答案要加上值域树状数组的查询
}
while(p <= mid) bit.update(a[p].c,1),tmp[++len] = a[p++]; // 归并左边剩下部分
while(q <= r) a[q].ans += bit.query(a[q].c),tmp[++len] = a[q++]; // 归并右边剩下部分
for(int i = l;i <= mid;++i) bit.update(a[i].c,-1); // 清空
for(int i = 1;i <= len;++i) a[l + i - 1] = tmp[i]; // 把临时数组里的值拷贝到原数组
}
第三步
由于第二步只能处理不存在相同的情况,接下来讲解如果存在 \(a_i=a_j,b_i=b_j,c_i=c_j\) 的 \((i,j)\) 该怎么处理。
注意刚才我们树状数组是这样更新的:bit.update(a[p].c,1)
这里的 1
实际上就是 \(a_s=a_p,b_s=b_p,c_s=c_p\) 的 \(s\) 的个数,记为 \(x\) ,\(x_i\) 我们在去重时求出。
因此代码如下:
int tmp[maxn];
void CDQ(int l,int r){
if(l == r) return;
int mid = l + r >> 1,p = l,q = mid + 1,len = 0;
CDQ(l,mid),CDQ(mid + 1,r);
while(p <= mid && q <= r){
if(a[p].b <= a[q].b) bit.update(a[p].c,a[p].x),tmp[++len] = a[p++];
else a[q].ans += bit.query(a[q].c),tmp[++len] = a[q++];
}
while(p <= mid) bit.update(a[p].c,a[p].x),tmp[++len] = a[p++];
while(q <= r) a[q].ans += bit.query(a[q].c),tmp[++len] = a[q++];
for(int i = l;i <= mid;++i) bit.update(a[i].c,-a[i].x);
for(int i = 1;i <= len;++i) a[l + i - 1] = tmp[i];
}
第四步
这时 CDQ 分治已经完成了,我们现在需要统计答案
按照刚才的代码,a[i].ans
表示 \(a_j\le a_i,b_j\le b_i,c_j\le c_i\) 但不包括 \(a_j=a_i,b_j=b_i,c_j=c_i\) 的 \(j\) 的个数,而 a[i].x
正好表示了 \(a_j=a_i,b_j=b_i,c_j=c_i\) 的 \(j\) 的个数。于是 a[i].ans + a[i].x
就是去重后第 \(i\) 个点的答案。
for(int i = 1;i <= cnt;++i) res[a[i].ans + a[i].x - 1] += a[i].x; // 注意是 + a[i].x,因为还有与 i 值相同所有 j,其总个数是 a[i].x
for(int i = 0;i < n;++i) printf("%d\n",res[i]);
\(\color{#52C41A}\texttt{AC CODE}\)
#include<stdio.h>
#include<algorithm>
const int maxn = 114514;
int n,k;
struct BIT{
int t[maxn << 1];
int lowbit(int i){return i & -i;}
void update(int i,int x){for(;i <= k;i += lowbit(i)) t[i] += x;}
int query(int i){int ans = 0;for(;i;i -= lowbit(i)) ans += t[i];return ans;}
} bit; // 树状数组
struct number{
int a,b,c,ans,x;
bool operator<(const number& y) const{return a != y.a ? a < y.a : b != y.b ? b < y.b : c < y.c;}
} a[maxn],tmp[maxn]; // 数的结构体
int res[maxn];
void CDQ(int l,int r){
if(l == r) return;
int mid = l + r >> 1,p = l,q = mid + 1,len = 0;
CDQ(l,mid),CDQ(mid + 1,r);
while(p <= mid && q <= r){
if(a[p].b <= a[q].b) bit.update(a[p].c,a[p].x),tmp[++len] = a[p++];
else a[q].ans += bit.query(a[q].c),tmp[++len] = a[q++];
}
while(p <= mid) bit.update(a[p].c,a[p].x),tmp[++len] = a[p++];
while(q <= r) a[q].ans += bit.query(a[q].c),tmp[++len] = a[q++];
for(int i = l;i <= mid;++i) bit.update(a[i].c,-a[i].x);
for(int i = 1;i <= len;++i) a[l + i - 1] = tmp[i];
}
int main(){
scanf("%d%d",&n,&k);
for(int i = 1;i <= n;++i) scanf("%d%d%d",&a[i].a,&a[i].b,&a[i].c),a[i].x = 1,a[i].ans = 0;
std::sort(a + 1,a + n + 1); // 排序
int cnt = 1;
for(int i = 2;i <= n;++i)
if(a[i].a == a[cnt].a && a[i].b == a[cnt].b && a[i].c == a[cnt].c) ++a[cnt].x; // 如果遇到与 i 一样的,x 值就要自加一
else a[++cnt] = a[i];
CDQ(1,cnt); // 注意不是 CDQ(1,n)
for(int i = 1;i <= cnt;++i) res[a[i].ans + a[i].x - 1] += a[i].x;
for(int i = 0;i < n;++i) printf("%d\n",res[i]);
return 0;
}
题解-P3810的更多相关文章
- BZOJ3262/洛谷P3810 陌上花开 分治 三维偏序 树状数组
原文链接http://www.cnblogs.com/zhouzhendong/p/8672131.html 题目传送门 - BZOJ3262 题目传送门 - 洛谷P3810 题意 有$n$个元素,第 ...
- 洛谷P3810 陌上花开(CDQ分治)
洛谷P3810 陌上花开 传送门 题解: CDQ分治模板题. 一维排序,二维归并,三维树状数组. 核心思想是分治,即计算左边区间对右边区间的影响. 代码如下: #include <bits/st ...
- P3810 陌上花开 CDQ分治
陌上花开 CDQ分治 传送门:https://www.luogu.org/problemnew/show/P3810 题意: \[ 有n 个元素,第 i 个元素有 a_i. b_i. c_i 三个属性 ...
- 2016 华南师大ACM校赛 SCNUCPC 非官方题解
我要举报本次校赛出题人的消极出题!!! 官方题解请戳:http://3.scnuacm2015.sinaapp.com/?p=89(其实就是一堆代码没有题解) A. 树链剖分数据结构板题 题目大意:我 ...
- noip2016十连测题解
以下代码为了阅读方便,省去以下头文件: #include <iostream> #include <stdio.h> #include <math.h> #incl ...
- BZOJ-2561-最小生成树 题解(最小割)
2561: 最小生成树(题解) Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1628 Solved: 786 传送门:http://www.lyd ...
- Codeforces Round #353 (Div. 2) ABCDE 题解 python
Problems # Name A Infinite Sequence standard input/output 1 s, 256 MB x3509 B Restoring P ...
- 哈尔滨理工大学ACM全国邀请赛(网络同步赛)题解
题目链接 提交连接:http://acm-software.hrbust.edu.cn/problemset.php?page=5 1470-1482 只做出来四道比较水的题目,还需要加强中等题的训练 ...
- 2016ACM青岛区域赛题解
A.Relic Discovery_hdu5982 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Jav ...
随机推荐
- PowerShell-3.多线程
$start = Get-Date $task1 = { $vUrl = 'http://img.mottoin.com/wp-content/uploads/2016/09/5-25.png' $v ...
- WireShark之抓telnet密码
背景环境: 涉及到telnet和ssh知识的时候,必须要提的是telnet传输是明文的不安全的,传输的密码都会泄露,有兴趣来做个试验. 一:工具 ·①Windows 7 PC机:②GNS3:③Wire ...
- 多线程-2.线程创建方式和Thread类
线程的创建方式 1.继承Thread类,重写run方法,示例如下: 1 class PrimeThread extends Thread { 2 long minPrime; 3 PrimeThrea ...
- 笔记·RCNN系相关
这篇博客总述了从RCNN到Mask RCNN的发展过程 https://blog.csdn.net/heavenpeien/article/details/80534963 简单的说,Fast RCN ...
- OO随笔之魔鬼的第一单元——多项式求导
OO是个借助Java交我们面向对象的课,可是萌新们总是喜欢带着面向过程的脑子去写求导,然后就是各种一面(main)到底.各种方法杂糅,然后就是被hack的很惨. 第一次作业:萌新入门面向对象 题目分析 ...
- CRM帮助初创企业降本增效的四个方法
对大部分初创公司来说,只有少数企业能够实现盈利,大部分只能维持盈亏平衡甚至是亏损.这是因为初创企业很难在短时间之内找到稳定的赢利点,而企业面临的风险和投入又是无法预知的.初创企业想要快速盈利,只能降低 ...
- MegaCli是一款管理维护硬件RAID软件,可以通过它来了解当前raid卡的所有信息,包括 raid卡的型号,raid的阵列类型,raid 上各磁盘状态
MegaCli 监控raid状态 转载weixin_30344131 最后发布于2015-10-16 13:05:00 阅读数 简介 MegaCli是一款管理维护硬件RAID软件,可以通过它来了 ...
- Ansible_使用文件模块将修改文件复制到受管主机
一.描述常用文件模块 1.常用文件模块 模块名称 模块说明 blockinfile 插入.更新或删除由可自定义标记线包围的多行文本块 copy 将文件从本地或远程计算机复制到受管主机上的某个位置.类似 ...
- 云计算OpenStack共享组件---信息队列rabbitmq(2)
一.MQ 全称为 Message Queue, 消息队列( MQ ) 是一种应用程序对应用程序的通信方法.应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们. 消息传 ...
- linux进阶之gitlab仓库搭建及免密使用
一.Gitlab简介 GitLab 是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具,并在此基础上搭建起来的web服务. 可通过Web界面进行访问公开的或者私人项目.它拥有与Github类 ...