[BZOJ1227]-[SDOI2009]虔诚的墓主人-这题还行

Orz在前面

本来是刷数据结构的题的,然后在hzwer的数据结构里看到这道题…
然后3个小时就没有了…
看题解看不懂,又去看别人代码,然后才…= =唉
但是这道题和BZOJ1818有相似之处,思想十分巧妙,这道题维护的是数据的前缀和,而1818则是将直线拆成端点用差分,感觉收获不小


题目(复制自BZOJ)

传送门qwq——>BZOJ1227

题目描述

小W 是一片新造公墓的管理人。公墓可以看成一块N×M 的矩形,矩形的每个格点,要么种着一棵常青树,要么是一块还没有归属的墓地。当地的居民都是非常虔诚的基督徒,他们愿意提前为自己找一块合适墓地。为了体现自己对主的真诚,他们希望自己的墓地拥有着较高的虔诚度。一块墓地的虔诚度是指以这块墓地为中心的十字架的数目。一个十字架可以看成中间是墓地,墓地的正上、正下、正左、正右都有恰好k 棵常青树。小W 希望知道他所管理的这片公墓中所有墓地的虔诚度总和是多少

输入

第一行包含两个用空格分隔的正整数N 和M,表示公墓的宽和长,因此这个矩形公墓共有(N+1) ×(M+1)个格点,左下角的坐标为(0, 0),右上角的坐标为(N, M)。第二行包含一个正整数W,表示公墓中常青树的个数。第三行起共W 行,每行包含两个用空格分隔的非负整数xi和yi,表示一棵常青树的坐标。输入保证没有两棵常青树拥有相同的坐标。最后一行包含一个正整数k,意义如题目所示。

输出

包含一个非负整数,表示这片公墓中所有墓地的虔诚度总和。为了方便起见,答案对2,147,483,648 取模。

样例数据

Sample Input 5 6
13
0 2
0 3
1 2
1 3
2 0
2 1
2 4
2 5
2 6
3 2
3 3
4 3
5 2
2
Sample output
6


题解

根据题意,要求的就是每个墓碑位置上的形成十字架的总数,这个总数可以用组合数来算。
数据规模很大,N,M都是1e9级别,但是树的数目W却只有1e5级别,这将导致很多墓碑四周都没有树,那么它的虔诚度为0,即对答案贡献为0。那么很明显对于这样的数据我们可以进行离散化。
下面的步骤都是建立在已经对所有点的横坐标离散化之后的基础上。

我们分别维护四个数组, L[i] , R[i] , U[i] , D[i] 分别代表,每个点左边(Left),右边(Right),上面(Up),下面(Down)分别有多少棵树,然后N方暴力枚举,求和即为答案。

for each point i:ans=CkL[i]CkR[i]CkU[i]CkP[i] f o r   e a c h   p o i n t   i : a n s = ∑ C L [ i ] k ∗ C R [ i ] k ∗ C U [ i ] k ∗ C P [ i ] k

= =但是是不能这样做的,N^2大暴力不TLE才怪呢,空间也不够

我们发现,对于两棵在同一横排的树(纵坐标相等),设为A树和B树。那么对于这两棵树中间的墓,它们的L[i]和R[i]都是一样的。利用这个性质,我们可以想办法一次性求一段区间的答案,这样就可以避免计算不必要的L[i]和R[i]

我们先将所有树以y为第一关键字x为第二关键字从小到大排序,这样就可以在O(n)时间内统计出对于该树的L,R,U,D(L表示,在它到它右边下一棵树的这段开区间里的点的L[i],类比R,U,D)
使用树状数组维护横行的前缀和,前缀和相减得到区间。

从下往上扫,考虑当前扫描到了某一高度,但是当这根线继续向上走的时候,原来被统计进入U的点,现在被统计进入了D。也就是说,每扫到一个点,就需要更新该点所在列的数据,现在的是C(U,k)*C(D,K),原来的是C(U+1,k)*C(D-1,k),也就是说,每次需要把当前点所在列的值更新为C(U,k)*C(D,K)-C(U+1,k)*C(D-1,k)。

主要操作就是以上,剩下的就是细节问题了= =


自带大常数的代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int ans , N , K , uninum , cnt[100005] , c[100005][11] ;

struct Point{
    int x , y , L , R , U , D ;
    bool operator < ( const Point &A ) const{
        return y < A.y || ( y == A.y && x < A.x ) ;
    }
}p[100005] ;

struct Uniq{
    int x , id ;
    bool operator < ( const Uniq &A ) const{
        return x < A.x ;
    }
}U[100005] ;

struct BIT{
    int b[100005] ;
    void clear(){
        memset( b , 0 , sizeof( b ) ) ;
    }
    void Modify( int x , int d ){
    //  printf( "Modify start :: x = %d  d = %d\n" , x , d ) ;
        for( ; x <= uninum ; x += x&(-x) )
            b[x] += d ;
    //  printf( "Modify ended\n" ) ;
    }
    int Query( int x ){
        if( x == 0 ) return 0 ;
    //  printf( "Query start :: x = %d\n" , x ) ;
        int rt = 0 ;
        for( ; x ; x -= x&(-x) ) rt += b[x] ;
    //  printf( "Query ended with :: %d\n" , rt ) ;
        return rt ;
    }
}B ;

void solve(){
    c[0][0] = 1 ;
    for( int i = 1 ; i <= N ; i ++ ){
        c[i][0] = 1 ;
        for( int j = 1 ; j <= i && j <= K ; j ++ )
            c[i][j] = c[i-1][j] + c[i-1][j-1] ;
    }
    sort( p + 1 , p + N + 1 ) ;
    for( int i = 1 , tmp = 0 ; i <= N ; i ++ ){
        tmp = ( p[i-1].y == p[i].y ? tmp + 1 : 1 ) ;
        cnt[ p[i].x ] ++ ;
        p[i].L = tmp ; p[i].D = cnt[ p[i].x ] ;
    }
    for( int i = N , tmp = 0 ; i >= 1 ; i -- ){
        tmp = ( p[i].y == p[i+1].y ? tmp + 1 : 1 ) ;
        p[i].R = tmp ; p[i].U = cnt[ p[i].x ] - p[i].D ;
    }

    B.clear() ;
    for( int i = 1 ; i <= N ; i ++ ){
        //printf( "M Q :: i = %d\n" , i ) ;
        B.Modify( p[i].x , c[ p[i].U ][K] * c[ p[i].D ][K] - B.Query( p[i].x ) + B.Query( p[i].x - 1 ) ) ;
        if( p[i-1].y == p[i].y )
            ans += c[ p[i-1].L ][K] * c[ p[i].R ][K] * ( B.Query( p[i].x - 1 ) - B.Query( p[i-1].x ) ) ;
    }
    printf( "%d" , ans & 2147483647 ) ;
}

int main(){
    scanf( "%*d%*d%d" , &N ) ;
    for( int i = 1 ; i <= N ; i ++ ){
        scanf( "%d%d" , &p[i].x , &p[i].y ) ;
        U[i].id = i ; U[i].x = p[i].x ;
    } scanf( "%d" , &K ) ;
    sort( U + 1 , U + N + 1 ) ;
    U[0].x = -1 ;
    for( int i = 1 ; i <= N ; i ++ ){
        if( U[i-1].x != U[i].x ) uninum ++ ;
        p[ U[i].id ].x = uninum ;
        //printf( "p[%d].x = %d\n" , U[i].id , p[ U[i].id ].x ) ;
    }
    solve() ;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值