BZOJ2727: [HNOI2012]双十字-树状数组

传送门

题意:

给定一个 RC R ∗ C 的01 矩阵,要求计算出这个 01 矩阵中有多少个双十字。

双十字由两条水平的和一条竖直的“1”线段组成,要求满足以下几个限制:

1.两条水平的线段不能在相邻的两行。

2.竖直线段上端必须严格高于两条水平线段,下端必须严格低于两条水平线段。

3.竖直线段必须将两条水平线段严格划分成相等的两半。

4.上方的水平线段必须严格短于下方的水平线段。

输出双十字的个数 mod 1,000,000,009 的值。

R,C,N10000,RC1000000 R , C , N ≤ 10000 , R ∗ C ≤ 1000000

Solution:

lr[i] l r [ i ] 表示从i开始表示最多能左右延伸多少

down[i] d o w n [ i ] 表示从i开始最多往下延伸多少

top[i]表示从i开始最多往上延伸多少

我们考虑枚举下端线段的中点j,然后对于每个上端线段的中点i,对答案的贡献有

lr[j]len=1min(len1,lr[i])top[i]down[j] ∑ l e n = 1 l r [ j ] m i n ( l e n − 1 , l r [ i ] ) ∗ t o p [ i ] ∗ d o w n [ j ]

min不是很好处理,我们想办法去掉它

尝试分类讨论:

lr[i]lr[j] l r [ i ] ≤ l r [ j ] 时,答案为 (lr[i]lr[j]lr[i](lr[i]+1)2)top[i]down[j] ( l r [ i ] ∗ l r [ j ] − l r [ i ] ∗ ( l r [ i ] + 1 ) 2 ) ∗ t o p [ i ] ∗ d o w n [ j ]

lr[i]lr[j] > l r [ i ] > l r [ j ] 时,答案为 lr[j](lr[j]1)2top[i]down[j] l r [ j ] ∗ ( l r [ j ] − 1 ) 2 ∗ t o p [ i ] ∗ d o w n [ j ]

对于每一列的连续的非0区间,我们只需要把关于i的东西全部记录下来,每次结合j进行计算即可

用树状数组维护3个值:

1. lr[i](lr[i]+1)2top[i] − l r [ i ] ∗ ( l r [ i ] + 1 ) 2 ∗ t o p [ i ]

2. lr[i]top[i] l r [ i ] ∗ t o p [ i ]

3. top[i] t o p [ i ]

因为R和C 不确定,数组可能开不下,所以我们把矩阵压成一维

注意代码里是实时计算top
代码:

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int l[10010],r[10010],lr[1200010],down[1200010],pos[1200010];
int tr[10010][3],n,m,p,ans,top=1;
const int mod=1e9+9;
int id(int x,int y){return (x-1)*m+y;}
int add(int id,int i,int x)
{
    //cout<<id<<" "<<i<<" "<<x<<endl;
    for (;i<=m;i+=(i&-i))
    {
        tr[i][id]+=x;
        if (tr[i][id]>=mod) tr[i][id]-=mod;
        if (tr[i][id]<0) tr[i][id]+=mod;
    }
}
int query(int id,int i)
{
    int ans=0;
    for (;i;i-=(i&-i))
    {
        ans+=tr[i][id];
        if (ans>=mod) ans-=mod;
        if (ans<0) ans+=mod;
    }
    return ans;
}
int main()
{
    scanf("%d%d%d",&n,&m,&p);
    for (int x,y,i=1;i<=p;i++) scanf("%d%d",&x,&y),pos[id(x,y)]=1;
    for (int i=1;i<=n;i++)
    {
        l[1]=(!pos[id(i,1)]);
        for (int j=2;j<=m;j++) if (!pos[id(i,j)]) l[j]=l[j-1]+1;else l[j]=0;
        r[m]=(!pos[id(i,m)]);
        for (int j=m-1;j>=1;j--) if (!pos[id(i,j)]) r[j]=r[j+1]+1;else r[j]=0;
        for (int j=1;j<=m;j++)
            lr[id(i,j)]=min(l[j],r[j])-1;
            //,cout<<i<<" "<<j<<" "<<lr[id(i,j)]<<endl;
    }
    for (int i=1;i<=m;i++)
    {
        down[id(n,i)]=(!pos[id(n,i)]);
        for (int j=n-1;j>=1;j--)
            if (!pos[id(j,i)]) down[id(j,i)]=down[id(j+1,i)]+1;
    }
    for (int i=1;i<=n*m;i++) if (down[i]) down[i]--;

    for (int i=1;i<=m;i++)
    {
        for (int j=1;j<=n;j++)
        {
            if (pos[id(j,i)]){memset(tr,0,sizeof(tr));top=j+1;continue;}
            ans=(1ll*query(0,lr[id(j,i)])*down[id(j,i)]+ans)%mod;
            ans=(1ll*query(1,lr[id(j,i)])*down[id(j,i)]%mod*lr[id(j,i)]+ans)%mod;
            ans=(1ll*(query(2,m)-query(2,lr[id(j,i)])+mod)*down[id(j,i)]%mod*lr[id(j,i)]*(lr[id(j,i)]-1)/2+ans)%mod;
        //  cout<<ans<<endl;
            if (j==1) continue;
            if (pos[id(j-1,i)]) continue;
            if (lr[id(j-1,i)])
            {
                add(0,lr[id(j-1,i)],(1ll*(-lr[id(j-1,i)]*(lr[id(j-1,i)]+1)/2)*(j-1-top)%mod+mod)%mod);
                add(1,lr[id(j-1,i)],lr[id(j-1,i)]*(j-1-top));
                add(2,lr[id(j-1,i)],j-1-top);
            }
        }
        memset(tr,0,sizeof(tr));top=1;
    }
    printf("%d",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值