BZOJ 1227: [SDOI2009]虔诚的墓主人 树状数组 组合数

1227: [SDOI2009]虔诚的墓主人

Time Limit: 5 Sec Memory Limit: 259 MB
Submit: 1324 Solved: 629

Description

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

Input

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

Output

包含一个非负整数,表示这片公墓中所有墓地的虔诚度总和。为了方便起见,答案对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

HINT

图中,以墓地(2, 2)和(2, 3)为中心的十字架各有3个,即它们的虔诚度均为3。其他墓地的虔诚度为0。

所有数据满足1 ≤ N, M ≤ 1,000,000,000,0 ≤ xi ≤ N,0 ≤ yi ≤ M,1 ≤ W ≤ 100,000, 1 ≤ k ≤ 10。存在50%的数据,满足1 ≤ k ≤ 2。存在25%的数据,满足1 ≤ W ≤ 10000。

注意:”恰好有k颗树“,这里的恰好不是有且只有,而是从>=k的树中恰好选k棵

Source


水题一道,我也就做了五个小时罢了,明天来写题解


第二天:今天考试AK啦,超开心!233
这道题的思路相当巧妙,我们要求所有墓的虔诚度之和,其实就是求每个墓,可以组合出多少十字架,然后计数算和
对于一座墓,我们对他的计算应该是上面的树和k的组合数,下面的树和k的组合数,左边的树和k的组合数,右边的树和k的组合数,这四个数的乘积
我们令l表示这棵树的右边的墓穴(到下一棵树为止)的左边有多少棵树
令r表示这棵树的左边的墓穴(到下一棵树为止)的右边有多少棵树
我们令u表示这棵树的下面的墓穴(到下一棵树为止)的上面有多少棵树
又令d表示这棵树的上面的墓穴(到下一棵树为止)的下面有多少棵树
而我们发现一个很神奇的事情,就是对于在同一行的树,我们可以固定两棵树,而在这两个点之间的所有墓(这两个点之间没有树了,我们细分到最小单位),于是这些墓的左边的树和k的组合数和右边的树和k的组合数都是一样的,所以我们计算这个区间的时候,左边的树和k的组合数和右边的树和k的组合数的乘积是作为常数来看待的,因此我们只需要知道这中间的所有点分别的上面的树和k的组合数乘以下面的树和k的组合数的和即可,那么我们怎么求呢?显然用树状数组就可以了,我们把一个点上面的树和k的组合数乘以下面的树和k的组合数作为这个点的信息,每次我们修改这个信息,修改的会在下一层用到,而这一层用到的都是上一层的,因此我们单点修改区间询问即可,搞一个这个东西的前缀和,每次修改,然后查询一下,累计答案即可


最后注意一下离散化x坐标,然后按照y坐标排序,然后注意一下预处理组合数就可以了
这道题真的是思路非常巧妙,也融合了几个知识点,对思维的训练也非常强

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 100010;
struct LSH { int x, id; }lsh[MAXN];
struct Tree { int x, y, l, r, u, d; } a[MAXN];
int cnt, n, k, C[MAXN], X, Y, sum[MAXN], c[MAXN][15]; 
void modify( int x, int v ) { for( ; x <= cnt; x += x & -x ) C[x] += v; }
int query(int x){ int tmp=0; for(; x; x -= x & -x ) tmp += C[x]; return tmp; }
bool cmp1( const Tree &a, const Tree &b ) {
    if( a.y != b.y ) return a.y < b.y;
    else             return a.x < b.x;
}
bool cmp0( const LSH &a, const LSH &b ) { return a.x < b.x; }

int main( ) {
    scanf( "%d%d%d", &X, &Y, &n );
    for( register int i = 1; i <= n; i++ ) {
        scanf( "%d%d", &a[i].x, &a[i].y );
        lsh[i].x = a[i].x; lsh[i].id = i;
    }
    sort( lsh + 1, lsh + n + 1, cmp0 );cnt = 0;
    lsh[0].x = -2147483640;
    for( register int i = 1; i <= n; i++ ) {
        if( lsh[i].x != lsh[ i - 1 ].x ) cnt++;
        a[lsh[i].id].x = cnt;
    }
    sort( a + 1, a + n + 1, cmp1 ); //按照y为第一关键字排序 

    int tmp = 0, ans = 0;
    for( register int i = 1; i <= n; i++ ) {
        if( a[i].y == a[ i - 1 ].y ) tmp++;
        else                         tmp = 1;
        a[i].l = tmp; sum[a[i].x]++;
        a[i].d = sum[a[i].x];
    }
    tmp = 0;
    for( register int i = n; i; i-- ) {
        if( a[i].y == a[ i + 1 ].y ) tmp++;
        else                         tmp = 1;
        a[i].r = tmp; a[i].u = sum[a[i].x] - a[i].d;
    }/*
    for( register int i = 1; i <= n; i++ ) {
        printf( "%a[%d]::%d %d %d %d %d %d\n", i, a[i].x, a[i].y, a[i].l, a[i].r, a[i].u, a[i].d );
    }*/
    c[0][0] = 1; scanf( "%d", &k );
    for( register int i = 1; i <= n; i++ ) { c[i][0] = 1;
        for( register int j = 1; j <= k && j <= i; j++ ) c[i][j] = c[ i - 1 ][j] + c[ i - 1 ][ j - 1 ];
    }/*
    printf( "\n" );
    for( register int i = 1; i <= n; i++ ) {
        for( register int j = 1; j <= k; j++ ) {
            printf( "%d ", c[i][j] );
        }
        printf( "\n" );
    }*/
    for( register int i = 1; i <= n; i++ ) {
        modify( a[i].x, c[a[i].d][k] * c[a[i].u][k] - query( a[i].x ) + query( a[i].x - 1 ) );
        if( i > 1 && a[i].y == a[ i - 1 ].y ) ans += c[a[ i - 1 ].l][k] * c[a[i].r][k] * ( query( a[i].x - 1 ) - query( a[ i - 1 ].x ) );
    }
    printf( "%d\n", ans&2147483647 );
    return 0;
}

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值