P2154 [SDOI2009]虔诚的墓主人

P2154 [SDOI2009]虔诚的墓主人


树状数组扫描线题

用树状数组维护一些互不相干的组合数的区间和

这一道题,如果从N,M入手。确实是不好做的

所以要考虑从W入手。

很容易发现,对于一块墓地,我们要求\(C_{\text{上方树的个数}}^k\cdot C_{\text{下方树的个数}}^k\cdot C_{\text{左方树的个数}}^k\cdot C_{\text{右方树的个数}}^k\)

然后又可以发现一条很明显的性质,在某一行,两颗树之间,\(C_{\text{左方树的个数}}^k\cdot C_{\text{右方树的个数}}^k\)是不变的。列也是如此。

某一列两棵树之间墓地虔诚值的部分组合数的乘积可以在O(1)内处理出来,剩下的便是算左右两边树的组合数乘积。

其实,在算这个的时候。很明显,对于在这之中的某一块墓地,我们不会动左右两边树,所以的两边树的组合数乘积也是不变的。我们所要做的便是将他们(连续的墓地的组合数)加起来。

我们需要优化的是这个过程,也就是区间加。然后就可以使用树状数组维护。

那这些组合数光讨论的不变,那什么时候变动呢?

首先对于上下两边树的组合数,肯定是在我们更换所以来的树的时候变换的。

那么左右两边树的呢?在我们扫描完一颗树的时候,这棵树就不会被使用了,就可以更改这个值了。

// luogu-judger-enable-o2
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using std::sort;
const int maxn=101000;
const long long mode=2147483648LL;
struct node
{
    int x,y;
    int X,Y;
    bool operator < (const node &a)const
    {
        if(x!=a.x)  return x<a.x;
        return y<a.y;
    }
};
node T[maxn];
long long last[maxn],Layt[maxn];
long long C[maxn][11];
long long ans;
int totX[maxn],totY[maxn];
int baseX[maxn],lenX;
int baseY[maxn],lenY;
int base[maxn];
int N,M,w,k;
int length;
void insert(long long val,int pos)//Begin:树状数组
{
    while(pos<=length)
    {
        base[pos]=(base[pos]+val)%mode;
        pos+=(pos&(-pos));
    }
    return ;
}
long long sum(int pos)
{
    if(pos==0)  return 0;
    long long res=0;
    while(pos)
    {
        res=(res+base[pos])%mode;
        pos-=(pos&(-pos));
    }
    return res;
}
long long query(int l,int r)
{
    return sum(r)-sum(l-1);
}//End:树状数组
void unique(int *A,int &Len)//去重
{
    sort(A+1,A+Len+1);
    int len=0,now=0x7fffffff;
    for(int i=1;i<=Len;i++)
        if(now!=A[i])
        {
            now=A[i];
            A[++len]=A[i];
        }
    Len=len;
    return;
}
void init(int x)//组合数
{
    C[0][0]=1;
    for(int i=1;i<=x;i++)
        for(int j=0;j<=10;j++)
            C[i][j]=(C[i-1][j]+(j?C[i-1][j-1]:0))%mode;
    return ;
}
int check(int *A,int len,long long val)//离散化查询
{
    int l=1,r=len;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(A[mid]>=val) r=mid;
        else    l=mid+1;
    }
    return l;
}
void account(int n)//统计离散化后每一列,每一行共有多少个树
{
    int nowX,nowY;
    for(int i=1;i<=n;i++)
    {
        nowX=check(baseX,lenX,T[i].x);
        nowY=check(baseY,lenY,T[i].y);
        totY[nowY]++;
        totX[nowX]++;
        T[i].X=nowX;T[i].Y=nowY;
    }
    length=lenY;
}
void add(int Y,int K)//将一颗树填进树状数组
{
    if(last[Y])//last为上一次填进去的数字
    {
        insert(-last[Y],Y);//删除
    	last[Y]=0;//置零
	}
    Layt[Y]++;//这一行中,已经扫过的的个数
    if(Layt[Y]>=K&&totY[Y]-Layt[Y]>=K)//满足计算的条件
    {
        insert((C[Layt[Y]][K]*C[totY[Y]-Layt[Y]][K])%mode,Y);//计算出新的组合数来,填进树状数组
        last[Y]=(C[Layt[Y]][K]*C[totY[Y]-Layt[Y]][K])%mode;//更新last
    }
}
long long clac(int l,int r)
{
	if(l>r)	return 0;//防止爆负数
    return query(l,r);//计算
}
void work(int n,int K)//解决问题
{
    int nowX=-1,now;
    for(int i=1;i<=n;i++)
    {
        if(nowX!=T[i].X)//与上一棵树不在同一列中
        {
            now=0;//计数器归零
            nowX=T[i].X;
        }
        now++;
        if(totX[nowX]-now>=k&&now>=K&&T[i+1].X==nowX)//满足同一列中的计算标准
            ans=(ans+(C[now][k]*(clac(T[i].Y+1,T[i+1].Y-1)*C[totX[nowX]-now][k])%mode)%mode)%mode;//计算
        add(T[i].Y,K);//将这颗树填进树状数组
    }
}
int main()
{
    scanf("%d%d%d",&N,&M,&w);
    for(int i=1;i<=w;i++)
    {
        scanf("%d%d",&T[i].x,&T[i].y);
        baseX[i]=T[i].x;
        baseY[i]=T[i].y;
    }
    scanf("%d",&k);
    unique(baseX,lenX=w);//离散化
    unique(baseY,lenY=w);
    sort(T+1,T+1+w);//按照x为第一关键字,y为第二关键字排序
    init(w);//预处理组合数
    account(w);//统计同一行、列中的树的个数
    work(w,k);//进行计算
    printf("%lld",(ans+mode)%mode);
}

posted @ 2018-10-19 20:04  Lance1ot  阅读(153)  评论(0编辑  收藏  举报