P2154 [SDOI2009]虔诚的墓主人

传送门

感觉题意有坑

害我看了半天

题目原文:


一块墓地的虔诚度是指以这块墓地为中心的十字架的数目。一个十字架可以看成中间是墓地,墓地的正上、正下、正左、正右都有恰好k棵常青树。


本来以为要刚好 k 颗,不能多了

结果看着样例一脸懵逼

才发现多的可以不管

比如有一行有 a 颗树,那么从这 a 颗中随便取 k 颗出来都是一种方案

所以有 C[ a ] [ k ] 种方案

终于看懂题意了,然后看看数据范围

靠,这么大怎么搞?

果断放弃,看题解去了

看着题解半天才懂,我太菜了      题解

可以发现树的数量不大

所以考虑把树的坐标离散化

考虑一个点的四个方向的树对它的贡献

设四个方向的树的数量为 u,d,l,r

那么总的贡献就是 C[u][k] * C[d][k] * C[l][k] * C[r][k]

考虑从左到右,从下到上的顺序扫描

同一个 x 坐标的的相邻两个树的中间空地

u和d是不变的

会变的是l,r

如果要一次计算一段空地的总方案数则需要对C[l][k],C[r][k]求和后再乘上 上面下面的方案

用树状数组来维护一段 y 坐标的C值

核心代码,实现维护一段 y 坐标的C值(重点)

for(int i=1;i<=w;i++)//此时每颗树已经离散好了
    {
        //注意树状数组维护的是一段y坐标的C的和
        if(i==1||a[i].x!=a[i-1].x) tt=0;
        tt++;//存当前x坐标下面已经经过了多少棵树
        int le=a[i].y,v=0;
        if( (++h[le]/*h存当前xy坐标的左边一段已经经过了多少棵树*/)>=K && 
        cnt[le]/*cnt[i]存y坐标为i的树的数量,即一行的树的数量*/-h[le]/*减h[le]是求出当前xy坐标右边一段的树的数量*/>=K )
            v=(1ll*C[ h[le] ][K]*C[ cnt[le]-h[le] ][K])%mo;//如果有贡献才计算
        change(le,v-r[le]/*r存的是同一高度上一次v的值*/); r[le]=v;//把此行多增加的值加入树状数组并更新r数组
        if(i==w||a[i].x!=a[i+1].x||a[i+1].y-a[i].y<=1||tt<K||tot[a[i].x]/*tot存的是x坐标为i的树的数量,即一列的树的数量*/-tt<K) continue;//注意一堆判断
        Ans=(Ans+1ll*C[tt][K]*C[ tot[a[i].x]-tt ][K]/*u和d可以直接求出来*/%mo * 
        ( query(a[i+1].y-1)-query(a[i].y)/*一段l和r的贡献用树状数组一次性求出来*/ )%mo)%mo;
    }

注释一堆...

来一发没注释的版本:

for(int i=1;i<=w;i++)
    {
        if(i==1||a[i].x!=a[i-1].x) tt=0;
        tt++;
        int le=a[i].y,v=0;
        if( (++h[le])>=K&&cnt[le]-h[le]>=K ) v=(1ll*C[ h[le] ][K]*C[ cnt[le]-h[le] ][K])%mo;
        change(le,v-r[le]); r[le]=v;
        if(i==w||a[i].x!=a[i+1].x||a[i+1].y-a[i].y<=1||tt<K||tot[a[i].x]-tt<K) continue;
        Ans=(Ans+1ll*C[tt][K]*C[ tot[a[i].x]-tt ][K]%mo*( query(a[i+1].y-1)-query(a[i].y) )%mo)%mo;
    }

然后就是离散化的操作和计算出cnt,tot和C的处理了

完整代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
const int N=1e5+7;
const long long mo=2147483648;
int w,C[N][17],tmp[N],tot[N],cnt[N],h[N],r[N],T[N],col,K;
long long Ans;
struct data
{
    int x,y;
}a[N];//存树的坐标
inline bool cmp1(const data &a,const data &b){ return a.x!=b.x ? a.x<b.x : a.y<b.y; }//按x,y排序
inline bool cmp2(const data &a,const data &b){ return a.y!=b.y ? a.y<b.y : a.x<b.x; }//按y,x排序

//树状数组的操作
inline void change(int x,int v){ while(x<=col) T[x]+=v,x+=(x&-x); }
inline int query(int x){ int res=0; while(x) res+=T[x],x-=(x&-x); return res; }

inline void pre()//预处理C
{
    C[0][0]=1;
    for(int i=1;i<=w;i++)
    {
        C[i][0]=1;
        for(int j=1;j<=min(i,K);j++)
            C[i][j]=C[i-1][j]+C[i-1][j-1];
    }
}
int main()
{
    read(); read();
    w=read();
    for(int i=1;i<=w;i++) a[i].x=read(),a[i].y=read();
    K=read();

    pre();

    sort(a+1,a+w+1,cmp1);//按xy排序
    int tt=0;
    for(int i=1;i<=w;i++)
    {
        if(i==1||a[i].x!=a[i-1].x) tt++;
        tmp[i]=tt;
    }
    for(int i=1;i<=w;i++) tot[ a[i].x=tmp[i] ]++;//把x离散顺便计算tot

    sort(a+1,a+w+1,cmp2); tt=0;//按yx排序
    for(int i=1;i<=w;i++)
    {
        if(i==1||a[i].y!=a[i-1].y) tt++;
        tmp[i]=tt;
    }
    for(int i=1;i<=w;i++) cnt[ a[i].y=tmp[i] ]++;//把y离散顺便计算cnt
    col=a[w].y;//树状数组的上限
    sort(a+1,a+w+1,cmp1);//重新xy排序

    for(int i=1;i<=w;i++)//注释之前代码有了,不再重复
    {
        if(i==1||a[i].x!=a[i-1].x) tt=0;
        tt++;
        int le=a[i].y,v=0;
        if( (++h[le])>=K&&cnt[le]-h[le]>=K ) v=(1ll*C[ h[le] ][K]*C[ cnt[le]-h[le] ][K])%mo;
        change(le,v-r[le]); r[le]=v;
        if(i==w||a[i].x!=a[i+1].x||a[i+1].y-a[i].y<=1||tt<K||tot[a[i].x]-tt<K) continue;
        Ans=(Ans+1ll*C[tt][K]*C[ tot[a[i].x]-tt ][K]%mo*( query(a[i+1].y-1)-query(a[i].y) )%mo)%mo;
    }
    printf("%lld",Ans);
    return 0;
}
原文地址:https://www.cnblogs.com/LLTYYC/p/9721331.html