[BZOJ 2727][HNOI 2012]双十字(树状数组+计数问题)

题目链接

?id=2727

思路

这个题好难啊啊啊啊啊啊啊啊啊啊啊啊啊,花了我半天的时间去研究vfk和ydc的题解才算大概搞懂 因为题目描述非常坑爹,竟然没有说各自的范围,只是说了的大小,因此我们只能开个1e6的一维数组,把二维的东西都压到一维里,这个比较好实现(以下用array[i,j]来表示二维数组array[i][j]在一维数组array[x]里)。 首先预处理出格子向左方向延伸、向右方向延伸、向下方向延伸的连续的’1’格子的最大长度。 然后求出为中心(横线与竖线的交点)向左右延伸的最大值(的一半)。 接着我们遍历棋盘中的每个格子,保存的是当前的竖线的点。 以上各种乱七八糟的东西如下图所示:

考虑假设我们已知点上面,并且已知,能构成多少个不同的双十字。 下面是上面的那个01矩阵中的几个不同的合法的双十字

那么我们看怎么来求这个方案数。。。

假设我们已经知道了下面的横线长度 然后下面的横线长度实际上不是固定的,那么我们需要枚举它,最终的方案数 恩,这时候我们离成功已经非常接近了。。。

看到那个拆掉 把上面的式子改一改: 1. 2.

就是个数列求和嘛,(2)是有求和公式的: 2.

代码

代码是扒的ydc的,因为开了内联,所以跑起来比ydc的标程快几百ms

using namespace std;typedef long long int LL;int n,m,tot; //tot=0的总数LL bit1[MAXN],bit2[MAXN],bit3[MAXN];LL ans;bool map[MAXN];int L[MAXN],R[MAXN],C[MAXN]; //L[i,j]=(i,j)的左边有多少个连续的1,R[i,j]=(i,j)的右边有多少个连续的1,(i,j)是0的话,L[i,j]=R[i,j]=-1,C[i,j]=min{L[i,j],R[i,j]}int down[MAXN]; //down[i,j]=(i,j)下面有多少个连续的1,(i,j)为0时down[i,j]=-1;int q[MAXN]; //类似于栈的神奇的东西。。。void add(LL sum[],int pos,LL val) //在树状数组sum[]的pos位置加上值val{while(pos<=m){sum[pos]=(sum[pos]+val)%MOD;pos+=lowbit(pos);}}LL query(LL sum[],int pos) //查询树状数组sum[]的前pos位置和{LL ans=0;while(pos>0){ans=(ans+sum[pos])%MOD;pos-=lowbit(pos);}return ans;}inline ) //二维下标(x,y)对应的一位下标{return (x-1)*m+y;}void prework(){//预处理L、R、C数组for(int i=1;i<=n;i++) //枚举第i行{if(map[calc(i,1)]) L[calc(i,1)]=-1; else L[calc(i,1)]=0; //预处理L[i,1]if(map[calc(i,m)]) R[calc(i,m)]=-1; else R[calc(i,m)]=0; //预处理R[i,m]for(int j=2;j<=m;j++){if(map[calc(i,j)]) L[calc(i,j)]=-1;else L[calc(i,j)]=L[calc(i,j-1)]+1;}for(int j=m-1;j>=1;j–){if(map[calc(i,j)]) R[calc(i,j)]=-1;else R[calc(i,j)]=R[calc(i,j+1)]+1;}for(int j=m;j>=1;j–) //?????C[calc(i,j)]=min(L[calc(i,j)],R[calc(i,j)]);}//预处理down数组for(int j=1;j<=m;j++) //枚举列j{if(map[calc(n,j)]) down[calc(n,j)]=-1; else down[calc(n,j)]=0; //初始化第j列最下面一行(第n行)的值for(int i=n-1;i>=1;i–){if(map[calc(i,j)]) down[calc(i,j)]=-1;else down[calc(i,j)]=down[calc(i+1,j)]+1;}}}void clear(int &top,int &tail) //暴力清零{top=-1;tail=0;for(int i=1;i<=m;i++)bit1[i]=bit2[i]=bit3[i]=0;}void solve() //计数求出答案{int top=-1,tail=0; //top是从当前格子开始向上延伸的最高点行标,tail是数组q的栈顶指针for(int j=1;j<=m;j++) //枚举列j{clear(top,tail);for(int i=1;i<=n;i++) //枚举行i{int now=calc(i,j); //now=当前的点在一维数组中的下标(top==-1) top=i; //top为-1,而当前格子为’1’,,那么top=当前格子高度else if(C[now]!=0) //now可以构成一条横线,而且它上面有格子当竖线{LL sum1=query(bit1,C[now])*C[now]%MOD;LL sum2=query(bit2,C[now]);LL sum3=C[now]query(bit3,m)-query(bit3,C[now]))%MOD; //sum3是利用了补集转化的思想LL t=(sum1-sum2+sum3+MOD)%MOD;ans=(ans+t*down[now])%MOD;if(q[tail]==calc(i-1,j)){int tmp=q[tail],h=(tmp-1)/m+1; //h是now的行标add(bit1,C[tmp],C[tmp]*(h-top)%MOD);add(bit2,C[tmp],C[tmp]h-top)%MOD);add(bit3,C[tmp],h-top);}q[++tail]=now; //将点now入队continue;}if(tail&&q[tail]==calc(i-1,j)){int tmp=q[tail],h=(tmp-1)/m+1; //h是now的行标add(bit1,C[tmp],C[tmp]*(h-top)%MOD);add(bit2,C[tmp],C[tmp]h-top)%MOD);add(bit3,C[tmp],h-top);}}}}int main(){scanf(“,&n,&m,&tot);for(int i=1;i<=tot;i++){int x,y;scanf(“%d%d”,&x,&y);map[calc(x,y)]=true;}prework();solve();printf(“%lld\n”,ans);return 0;}

走一个地方停一个地方。在我心里最美好的就是和你一起老在路上,

[BZOJ 2727][HNOI 2012]双十字(树状数组+计数问题)

相关文章:

你感兴趣的文章:

标签云: