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

142 篇文章 0 订阅
98 篇文章 0 订阅

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=2727

思路

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

考虑假设我们已知点 i,j 在同一列,且 ji 上面,并且已知 C[i]C[j] ,能构成多少个不同的双十字。
下面是上面的那个01矩阵中的几个不同的合法的双十字
这里写图片描述

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

假设我们已经知道了下面的横线长度 leni ,然后再求方案数,这个比较显然是方案数 delta=lenimin{leni1,C[j]}(topj)down[i]
然后下面的横线长度实际上不是固定的,那么我们需要枚举它,最终的方案数 delta=C[i]len=1lenmin{len1,C[j]}(topj)down[i]
恩,这时候我们离成功已经非常接近了。。。

看到那个 min 确实非常恶心,我们可以通过分类讨论把 min 拆掉
把上面的式子改一改:
1. C[i]<=C[j],delta=C[i]len=1len(len1)(topj)down[i]
2. C[i]>C[j],delta=C[i]len=1lenC[j](topj)down[i]

就是个数列求和嘛,(2)是有求和公式的:
2. C[i]>C[j],delta=C[i]len=1len(len1)(topj)down[i]
delta=C[i]len=1(len2len)(topj)down[i]
delta=(C[i]len=1len2 C[i]len=1len)(topj)down[i]

代码

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 2000100
#define lowbit(x) ((x)&(-(x)))
#define MOD 1000000009

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 int calc(int x,int y) //二维下标(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=当前的点在一维数组中的下标
            if(map[now]) clear(top,tail); //now是个0,那么和上面1~i-1行的图形连不起来,将top置为-1,tail和数组q置为0
            else if(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]*(C[now]-1)/2%MOD*(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]*(C[tmp]+1)/2%MOD*(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]*(C[tmp]+1)/2%MOD*(h-top)%MOD);
                add(bit3,C[tmp],h-top);
            }
        }
    }
}

int main()
{
    scanf("%d%d%d",&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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值