题解 P3810 【【模板】三维偏序(陌上花开)】

题解 P3810 【【模板】三维偏序(陌上花开)】

三位偏序: 只考虑有多少个f,不考虑每个f具体 是f几; 第一维,排序。 接下来,只要满足bi<=bj,ci<=cj,(i<j) 那么,i就是j的一个满足条件的元素(i对j造成了贡献)。

怎么同时满足b,c的关系,并且不用再cmp排序? 想到要找到满足(i<j)的同时的b,c。 可以边对b排序,边统计c。 先看b满足条件吗?

用归并排序,排着b点。 归并排序中,对两个已经排好序的序列处理.

1.对于右边的j,如果左边的i,满足b[i]<b[j], 就把c[i]添加到树状数组里面,等待将来统计h到i中的元素对j产生的贡献。 如果不满足,即b[i]>b[j],那么以后的(i到t)都不可能对j产生贡献了, 这时候直接统计树状数组里面能对j产生贡献的。

2.如果一个序列已经拍完了,这时还剩一个序列。 还剩右边的,说明h到mid的元素都满足b<b[j],直接树状数组统计。 还剩左边的,已经没机会统计了。

3.最后别忘了将本次树状数组清空!

注意:对于相同的要去重。因为如果所有都不同,那么如果i对j产生贡献,则j不可能对i产生贡献。 但如果有相同的,则j会对i产生贡献。

而归并排序时,只有左边的会对右边的产生贡献,因此得去重后单独统计贡献。

结尾统计答案: f[j]的统计: 对于一个j,有和它不想等的,直接加上。

和它相同的,加(和j相同的元素的个数-1) 而有多少个这样的f[j]呢,有 和j 相同的元素的个数 个f[j]

#include<ctime>
#include<map>
#include<cstdlib>
#include<cmath>
#define r(i,a,b) for(int i=a;i<=b;i++)
#define rr(i,a,b) for(int i=a;i>=b;i--)
#define inf 0x3f3f3f3f
#define mem(a) memset(a,0x3f,sizeof(a))
#define re(a) a=read()
#define me(a) memset(a,0,sizeof(a))
#define in inline
using namespace std;
const int N=100007;
inline int read(){
    char ch=getchar();
    int w=1,x=0;
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0',ch=getchar();}
    return x*w;
}
int n,M,cnt=0,tr[N<<1],ans[N<<1];
struct node{
    int a,b,c,num,contribution;
}e[N],temp[N];
in bool cmp(node x,node y){
    return (x.a==y.a)?((x.b==y.b)?x.c<y.c:x.b<y.b):x.a<y.a;
}
in int lowbit(int x){
    return x&(-x);
}
in void add(int x,int w){
    for(;x<=M;x+=lowbit(x))tr[x]+=w;
}
in int sum(int x){
    int res=0;
    for(;x;x-=lowbit(x))res+=tr[x];
    return res;
}
in void backsort(int h,int t){
    if(h==t)return;
    int mid=(h+t)>>1;
    backsort(h,mid),backsort(mid+1,t);
    int l=h,r=mid+1,tot=h;
    while(l<=mid&&r<=t){
        if(e[l].b<=e[r].b){
            add(e[l].c,e[l].num);
            temp[tot++]=e[l++];
        }
        else e[r].contribution+=sum(e[r].c),temp[tot++]=e[r++];
    }
 /*
 对于右边的j,如果左边的i,满足b[i]<b[j],
就把c[i]添加到树状数组里面,等待将来统计h到i中的元素对j产生的贡献。
如果不满足,即b[i]>b[j],那么以后的(i到t)都不可能对j产生贡献了,
这时候直接统计树状数组里面能对j产生贡献的。                          
                           */                          
    while(l<=mid)add(e[l].c,e[l].num),temp[tot++]=e[l++];
                                                 /*
如果一个序列已经拍完了,这时还剩一个序列。
还剩右边的,说明h到mid的元素都满足b<b[j],直接树状数组统计。
还剩左边的,已经没机会统计了。                                */

    while(r<=t)e[r].contribution+=sum(e[r].c),temp[tot++]=e[r++];
    r(i,h,mid)add(e[i].c,-e[i].num);
    r(i,h,t)e[i]=temp[i];
}
int main(){
    //freopen("test.in","r",stdin);
    //freopen("ac.out","w",stdout);
    re(n),re(M);
    r(i,1,n){
        re(e[i].a),re(e[i].b),re(e[i].c);
        e[i].num=1;
    }
    sort(e+1,e+1+n,cmp);
    e[++cnt]=e[1];
    r(i,2,n){
        if(e[cnt].a==e[i].a&&e[cnt].b==e[i].b&&e[cnt].c==e[i].c)e[cnt].num++;
        else e[++cnt]=e[i];
    }
    backsort(1,cnt);
    r(i,1,cnt)ans[e[i].contribution+e[i].num-1]+=e[i].num;
    r(i,0,n-1)printf("%d\n",ans[i]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值