(正经题解在后面)斜体字都是一年前在没有把cdq扯清楚的情况下应付的,即使现在真正理解了cdq,还是将这堆话留在这,毕竟,花无重开日,人无再少年——
RunID | User | Problem | Result | Memory | Time | Language | Code_Length | Submit_Time |
2374693 | 2711694897 | 3262 | Accepted | 6376 kb | 1632 ms | C++/Edit | 1851 B | 2017-10-23 19:52:20 |
2374687 | 2711694897 | 3262 | Accepted | 6376 kb | 3160 ms | C++/Edit | 1852 B | 2017-10-23 19:50:45 |
先说点题外话:一张图告诉你函数开销有多大,用重载的"<"运算符替换一个cmp函数可以省接近50%的时间!!!就两行的cmp函数都能带来如此大的代价orz。回头再看NOIP渐渐开始卡常数,以后只要思路清晰不影响排版,能不写函数就不写函数吧。。。
题解:
比较模板化的三维偏序。
第一维排序,第二维cdq分治,第三维树状数组。
每一次处理[l,mid]对[mid+1,r]的影响时,比较第二维,查询第三维(因为[l,mid]区间的x一定小于[mid+1,r]区间的x,第三维用树状数组维护满足条件的个数),当第二维符合要求时,将它加入树状数组中。每次更新[mid+1,r]区间的一个ans(记得处理完后还原树状数组)。
————————————————————————————————————————————————————————
正解:cdq分治+树状数组
先对第一维a排序,于是这一维后面可以先不管。
对第二维b进行cdq分治,将区间[l,r]二分成[l,mid]和[mid+1,r],用双指针扫左右区间,i扫左区间,j扫右区间。(最不好叙述的操作来了)用所有满足p[i].b<=p[j].b的i在树状数组p[i].c处进行add操作(加上i的出现次数,不一定是加1),然后用对应的p[j].c在树状数组中去查询(注意:所有add过的每次移动j指针前要减回去)。
双指针每扫一遍为O(n),把递归每一层都算上的话相当于扫了logn次,所以总复杂度为O(nlogn)。
问的是严格不大于自己的元素有i个,这样的元素有多少个(这种问法比较适合用定语从句来说233),所以cdq分治完后还有稍微转换一下得到答案f数组。
注意:必须先cdq函数中必须先递归处理左右子区间再处理当前区间。因为按照第二维b排序是会打乱原本第一维a的升序的!!!如果先处理当前区间,那么等你sort两下在操作一波再递归子区间时子区间已经不满足第一维a升序了。但是如果先处理子区间,无论你子区间那一层怎么排,当前层的第一维a仍然满足左区间小于右区间,这才可以放心大胆地去操作后两维。
#include
#include
#include
#include
using namespace std;
const int N=1e5+4;
int c[N<<1],n,nn=0,lim,f[N];
struct Node {
int a,b,c,rep,ans;
friend bool operator <(const Node &p,const Node &q) {//cmpb
return p.b'9') c=getchar();
while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x;
}
inline void add(int x,int val) {
for (int i=x;i<=lim;i+=i&-i) c[i]+=val;
}
inline int query(int x) {
int ret=0;
for (int i=x;i;i-=i&-i) ret+=c[i];
return ret;
}
void cdq(int l,int r) {
if (l==r) return ;
int mid=l+r>>1;
cdq(l,mid);
cdq(mid+1,r);
sort(p+l,p+mid+1);//cmpb
sort(p+mid+1,p+r+1);//cmpb
int i=l,j=mid+1;
while (j<=r) {
while (i<=mid&&p[i].b<=p[j].b) add(p[i].c,p[i].rep),++i;
p[j].ans+=query(p[j].c),++j;
}
for (int k=l;k