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);
}