[CQOI2011]动态逆序对 CDQ分治
洛谷上有2道相同的题目(基本是完全相同的,输入输出格式略有不同)
CDQ分治
首先由于删除是很不好处理的,所以我们把删除改为插入,然后输出的时候倒着输出即可
首先这是一个类似于3维偏序的问题:
那么为了理解我的算法,这里先放出我对三维偏序的理解。
三维偏序实质上是在所有二维偏序中所有符合条件的数对中进一步找出符合另一个限制条件的数对。
这里找所有二维偏序中所有符合条件的数对可以用归并排序(就是相当于归并排序求逆序对)
那么树状数组求逆序对是个怎么回事呢?
相当于单独统计每个数对答案的贡献,由于树状数组的特殊性(前缀和)以及不重不漏的原则,我们只统计在一个数前面出现的,即数对(a,b)中,这个数对对答案的贡献只统计在b中(下标靠后的那个)
那么知道这些后我们就可以开始解题了!
将删除转为插入后,问题就可以转化为一个类似于三维偏序的问题:
对于每个数,我们只统计在它之前被插入的数对它造成的贡献(没有被删除的数默认为第一个插入,不然树状数组统计不了)
举个栗子:
我们观察样例: 如果是删除的话,变化过程是这样的(这里为了简化以方便理解,我们暂时不看最后一个删除)
5 4 2 6 3 1
5 2 6 3 1
2 6 3 1
如果是插入的话,变化过程自然就是反过来
2 6 3 1 ---> ans=4
5 2 6 3 1 ---> ans=7
5 4 2 6 3 1 ---> ans=11
此处注意:由于将删除变为了插入,因此最先删除的反而是最后插入的,下面的编号就是插入顺序
数字 | 5 | 4 | 2 | 6 | 3 | 1 |
---|---|---|---|---|---|---|
编号(t) | 2 | 3 | 1 | 1 | 1 | 1 |
统计到的贡献 | 0 | 1 | 0 | 0 | 1 | 3 |
这里由于我们是分时间输出,而不是分数字输出,因此我们将每个数字的贡献放入它所对应的编号内
所以我们的ans数组:
ans | 4 | 0 | 1 | |||
---|---|---|---|---|---|---|
下标 | 1 | 2 | 3 |
又由于编号和输出顺序是相反的,并且观察插入的特点,逆序对在插入某个数后不可能会变少,只会不变or上升,因此我们的答案应该是对于ans数组的前缀和,并且要倒着输出这个前缀和
那么我们将会输出 4 4 5
嗯?好像有哪里不对?
为什么呢?
我们可以发现,我们其实是单独统计了每个数在插入后的贡献,
但是我们就像传统的树状数组求逆序对一样求在一个数前面的数对是不行的
why?
树状数组求逆序对对于每个数只求在它前面的数对之所以可行,就是因为在它后面的数对会被在它后面的那个数所统计到
但是在这里,对于一个被删除的数来说,它可以统计到别的数,别的数可统计不到它啊
那这么办呢?
也许我们可以考虑%&%……##@#¥等一大堆妙妙的算法,
但是!归并排序并不慢啊!
所以我们考虑暴力一点的解法,
既然只统计前面的,会漏掉后面的,
那么我们就统计两次啊!一次统计前面的,一次统计后面的,加在一起不就好了
但是我们会观察到,只有被删除的数才会漏掉应该被统计到的贡献,而没有被删除的数是不会的,所以如果我们统计两次,那么被删除的数会统计到正确答案,但是没有被删除的数会统计到双倍贡献,但是这并没有什么影响,在统计完后把没有被删除的数的贡献和/2即可
第一次归并排序是要筛选出在一个数前面的并且比它大的数(符合基础逆序对)
第二次归并排序是要筛选出在一个数后面的并且比它小的数(符合基础逆序对)
以下是代码(注意两次归并排序由于要筛选出的东西是不同的(大小关系和下标关系头不同,因此在细节上略有差别):
--- 2018.10.04 优化了代码格式 ---
---2018.11.04 发现第二份代码格式没改,,,又改了一下---
下面为2题的代码
#include<bits/stdc++.h>
using namespace std;
#define R register int
#define AC 40100
#define lowbit(x) (x&(-x))
#define LL long long
#define D printf("line in %d\n",__LINE__);
int n, m, tot = ;
int c[AC];//这。。。不需要离散化?
LL ans[AC];//还是比较妙的?改删除为插入,这样就是三维偏序问题,然后统计答案的时候,
//计算时,一个数的贡献统计入时间靠后的那个数的ans[时间]里
//每次都加上前一个的时间贡献(前缀和),这样就可以分别统计到删除前的和删除后的ans。
struct node{
int t, num;
}s[AC], tmp[AC], s1[AC]; inline int read()
{
int x = ;char c = getchar();
while(c > '' || c < '') c = getchar();
while(c >= '' && c <= '') x = x * + c - '', c = getchar();
return x;
} void pre()
{
int a;
n = read(), m = read();
tot = m + ;//error!!!因为删除变成了插入,所以第一个删除的反而是最后一个插入的
for(R i = ; i <= n; i++)
s[i].num = read(),s[i].t = ,s1[i].num = s[i].num,s1[i].t = ;
for(R i = ; i <= m; i++) a = read(), s[a].t = tot, s1[a].t = tot--;
m ++;
} inline void add(int x){
for(R i = x; i <= m; i += lowbit(i)) c[i]++;//error!!!这里的m可能超过n,所以只添加到n是不够的,可能会导致查询不到
} inline void cut(int x){
for(R i = x; i <= m;i += lowbit(i)) c[i]--;
} inline void search(node x){
for(R i = x.t; i;i -= lowbit(i)) ans[x.t] += c[i];//直接累加到对应的时间里
} void merge_sort(int l, int r)//归并,按num排序,用树状数组统计合法的t
{
int mid = (l + r) >> , t = ;
if(l == r) return ;
if(l < r) merge_sort(l, mid), merge_sort(mid + , r);
int i = l, j = mid + ;
while(i <= mid && j <= r)
{//因为num已经符合,所以只要找在它前面的就好了
if(s[i].num > s[j].num) add(s[i].t), tmp[++t] = s[i++];
else search(s[j]), tmp[++t] = s[j++];
}
while(i <= mid) add(s[i].t), tmp[++t] = s[i++];
while(j <= r) search(s[j++]);
for(R i = l; i <= mid; i++) cut(s[i].t);
for(R i = ; i <= t; i++) s[l + i - ] = tmp[i];
} void merge_sort_two(int l, int r)//因为上面会少统计,所以这里再统计一次
{
int mid = (l + r) >> , t = ;
if(l == r) return ;
if(l < r) merge_sort_two(l, mid), merge_sort_two(mid + , r);
int i = l, j = mid + ;
while(i <= mid && j <= r)
{//因为num已经符合,所以只要找在它前面的就好了
if(s1[j].num < s1[i].num) add(s1[j].t), tmp[++ t] = s1[j ++];
else search(s1[i]), tmp[++ t] = s1[i ++];
}
while(j <= r) add(s1[j].t), tmp[++ t] = s1[j ++];
while(i <= mid) search(s1[i]), tmp[++ t] = s1[i ++];
for(R i = mid + ; i <= r; i ++) cut(s1[i].t);
for(R i = ; i <= t; i ++) s1[l + i - ] = tmp[i];
} void work()
{
ans[] >>= ;
for(R i = ; i <= m; i ++) ans[i] += ans[i - ];
for(R i = m; i > ; i --) printf("%lld ", ans[i]);
printf("%lld", ans[]);
}
int main()
{
freopen("in.in", "r", stdin);
pre();
merge_sort(, n);
merge_sort_two(, n);
work();
fclose(stdin);
return ;
}
#include<bits/stdc++.h>
using namespace std;
#define R register int
#define AC 100100
#define lowbit(x) (x&(-x))
#define LL long long
#define D printf("line in %d\n",__LINE__);
int n, m, tot = ;
int c[AC], w[AC];//这。。。不需要离散化?
LL ans[AC];//还是比较妙的?改删除为插入,这样就是三维偏序问题,然后统计答案的时候,
//计算时,一个数的贡献统计入时间靠后的那个数的ans[时间]里
//每次都加上前一个的时间贡献(前缀和),这样就可以分别统计到删除前的和删除后的ans。
struct node{
int t, num;
}s[AC], tmp[AC], s1[AC]; inline int read()
{
int x = ;char c = getchar();
while(c > '' || c < '') c = getchar();
while(c >= '' && c <= '') x = x * + c - '', c = getchar();
return x;
} void pre()
{
int a;
n = read(), m = read();
tot = m + ;//error!!!因为删除变成了插入,所以第一个删除的反而是最后一个插入的
for(R i = ; i <= n; i ++)
{
s[i].num = read(), s[i].t = ;
s1[i].num = s[i].num, s1[i].t = , w[s[i].num] = i;//error!!!这里是删除元素,给的是元素,不是下标
}
for(R i = ; i <= m; i ++)
a = read(), s[w[a]].t = tot, s1[w[a]].t = tot--;
m ++;
} inline void add(int x){
for(R i = x; i <= m; i += lowbit(i)) c[i] ++;//error!!!这里的m可能超过n,所以只添加到n是不够的,可能会导致查询不到
} inline void cut(int x){
for(R i = x; i <= m; i += lowbit(i)) c[i] --;
} inline void search(node x){
for(R i = x.t; i ;i -= lowbit(i)) ans[x.t] += c[i];//直接累加到对应的时间里
} void merge_sort(int l, int r)//归并,按num排序,用树状数组统计合法的t
{
int mid = (l + r) >> , t = ;
if(l == r) return ;
if(l < r) merge_sort(l, mid), merge_sort(mid + , r);
int i = l, j = mid + ;
while(i <= mid && j <= r)
{
if(s[i].num > s[j].num) add(s[i].t), tmp[++ t] = s[i ++];
else search(s[j]), tmp[++t]=s[j++];
}
while(i <= mid) add(s[i].t), tmp[++t]=s[i++];
while(j <= r) search(s[j++]);
for(R i = l; i <= mid; i ++) cut(s[i].t);
for(R i = ; i <= t; i ++) s[l + i - ] = tmp[i];
} void merge_sort_two(int l, int r)//因为上面会少统计,所以这里再统计一次
{
int mid=(l + r) >> , t = ;
if(l == r) return ;
if(l < r) merge_sort_two(l,mid), merge_sort_two(mid+,r);
int i = l, j = mid + ;
while(i <= mid && j <= r)
{
if(s1[j].num < s1[i].num) add(s1[j].t), tmp[++ t] = s1[j ++];
else search(s1[i]), tmp[++ t] = s1[i ++];
}
while(j <= r) add(s1[j].t), tmp[++ t] = s1[j ++];
while(i <= mid) search(s1[i]), tmp[++t]=s1[i++];
for(R i = mid + ; i <= r; i ++) cut(s1[i].t);
for(R i = ; i <= t; i ++) s1[l + i - ] = tmp[i];
} void work()
{
ans[] >>= ;
for(R i = ; i <= m; i ++) ans[i] += ans[i - ];
for(R i = m; i > ; i --) printf("%lld\n", ans[i]);
}
int main()
{
// freopen("in.in","r",stdin);
pre();
merge_sort(,n), merge_sort_two(,n);
work();
// fclose(stdin);
return ;
}
[CQOI2011]动态逆序对 CDQ分治的更多相关文章
- [BZOJ3295][Cqoi2011]动态逆序对 CDQ分治&树套树
3295: [Cqoi2011]动态逆序对 Time Limit: 10 Sec Memory Limit: 128 MB Description 对于序列A,它的逆序对数定义为满足i<j,且 ...
- 【BZOJ3295】[Cqoi2011]动态逆序对 cdq分治
[BZOJ3295][Cqoi2011]动态逆序对 Description 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依 ...
- bzoj3295: [Cqoi2011]动态逆序对(cdq分治+树状数组)
3295: [Cqoi2011]动态逆序对 题目:传送门 题解: 刚学完cdq分治,想起来之前有一道是树套树的题目可以用cdq分治来做...尝试一波 还是太弱了...想到了要做两次cdq...然后伏地 ...
- BZOJ3295 [Cqoi2011]动态逆序对 —— CDQ分治
题目链接:https://vjudge.net/problem/HYSBZ-3295 3295: [Cqoi2011]动态逆序对 Time Limit: 10 Sec Memory Limit: 1 ...
- 洛谷 P3157 [CQOI2011]动态逆序对 | CDQ分治
题目:https://www.luogu.org/problemnew/show/3157 题解: 1.对于静态的逆序对可以用树状数组做 2.我们为了方便可以把删除当成增加,可以化动为静 3.找到三维 ...
- BZOJ 3295: [Cqoi2011]动态逆序对 [CDQ分治]
RT 传送门 首先可以看成倒着插入,求逆序对数 每个数分配时间(注意每个数都要一个时间)$t$,$x$位置,$y$数值 $CDQ(l,r)$时归并排序$x$ 然后用$[l,mid]$的加入更新$[mi ...
- BZOJ3295:[CQOI2011]动态逆序对(CDQ分治)
Description 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计 ...
- P3157 [CQOI2011]动态逆序对 CDQ分治
一道CDQ分治模板题简单来说,这道题是三维数点对于离线的二维数点,我们再熟悉不过:利用坐标的单调递增性,先按更坐标排序,再按纵坐标排序更新和查询时都直接调用纵坐标.实际上,我们是通过排序将二维中的一维 ...
- LUOGU P3157 [CQOI2011]动态逆序对(CDQ 分治)
传送门 解题思路 cdq分治,将位置看做一维,修改时间看做一维,权值看做一维,然后就转化成了三维偏序,用排序+cdq+树状数组.注意算删除贡献时要做两次cdq,分别算对前面和后面的贡献. #inclu ...
随机推荐
- 通过java反射机制获取该类的所有属性类型、值
转自:http://blog.csdn.net/sd4000784/article/details/7448221 方法使用了这俩个包下的 field 和method import Java.lang ...
- python 布尔值 bool( ) 与逻辑运算符
逻辑运算符 not and or 运算符优先级 not > and >or printer(x or y) x为非零,则返回x,否则返回y print(1 or 2) print(3 o ...
- 高能福利 |"荐"者有份,有"福"同享
WeTest 导读 越来越多的开发者加入WeTest大家庭了,感谢大家一直以来的支持,WeTest又有新一步“大福利”赠送了! 即日起,参加WeTest用户推荐有礼活动,推荐者和被推荐者皆可获得福利. ...
- linux 命令缩写
su super user apt advanced packaging tool ifconfig interface configuration so shared object fsp frac ...
- Professional Books
Machine Learning: Pattern Recognition and Machine Learning(PRML) https://mqshen.gitbooks.io/p ...
- iframe高度自适应的6个方法
原文链接:http://caibaojian.com/iframe-adjust-content-height.html JS自适应高度,其实就是设置iframe的高度,使其等于内嵌网页的高度,从而看 ...
- ZOJ 3946 Highway Project 贪心+最短路
题目链接: http://www.icpc.moe/onlinejudge/showProblem.do?problemCode=3946 题解: 用dijkstra跑单元最短路径,如果对于顶点v,存 ...
- C++进阶之_类型转换
C++进阶之_类型转换 1.类型转换名称和语法 C风格的强制类型转换(Type Cast)很简单,不管什么类型的转换统统是: TYPE b = (TYPE)a C++风格的类型转换提供了4种类型转换操 ...
- B-2阶段组员分数分配
组名: 新蜂 组长: 武志远 组员: 宫成荣 谢孝淼 杨柳 李峤 项目名称: java俄罗斯方块 武 武 武 武 杨 宫 宫 杨 宫 谢 李 杨 李 谢 李 谢 李 谢 杨 宫 扬 谢 宫 李 武 评 ...
- HDU 2105 The Center of Gravity
http://acm.hdu.edu.cn/showproblem.php?pid=2105 Problem Description Everyone know the story that how ...