[BZOJ2727][HNOI2012]双十字(计数+树状数组)

考虑一个暴力的 O(R2C) O ( R 2 C ) 算法:
先预处理出:
l[i,j] l [ i , j ] :位置 (i,j) ( i , j ) 向左延伸到最多的 1 的个数(不包括 (i,j) ( i , j ) 本身)
r[i,j] r [ i , j ] :位置 (i,j) ( i , j ) 向右延伸到最多的 1 的个数(不包括 (i,j) ( i , j ) 本身)
u[i,j] u [ i , j ] :位置 (i,j) ( i , j ) 向上延伸到最多的 1 的个数(不包括 (i,j) ( i , j ) 本身)
d[i,j] d [ i , j ] :位置 (i,j) ( i , j ) 向下延伸到最多的 1 的个数(不包括 (i,j) ( i , j ) 本身)
并设 lr[i,j]=min(l[i,j],r[i,j]) l r [ i , j ] = min ( l [ i , j ] , r [ i , j ] )
那么先枚举列(记为第 k k 列),再枚举第 k 列上的一对合法位置(记为 (i,k) ( i , k ) (j,k) ( j , k ) ) ,并把 (i,k) ( i , k ) 作为双十字的上交点, (j,k) ( j , k ) 作为双十字的下交点,那么这样分 lr[i,k]lr[j,k] l r [ i , k ] ≥ l r [ j , k ] lr[i,k]<lr[j,k] l r [ i , k ] < l r [ j , k ] 两种情况处理。
如果 lr[i,k]lr[j,k] l r [ i , k ] ≥ l r [ j , k ] ,那么会产生的双十字个数为:

lr[j,k]×(lr[j,k]1)2×u[i,k]×d[j,k] l r [ j , k ] × ( l r [ j , k ] − 1 ) 2 × u [ i , k ] × d [ j , k ]

否则会产生的双十字个数为:
(lr[i,k]×lr[j,k]lr[i,k]×(lr[i,k]+1)2)×u[i,k]×d[j,k] ( l r [ i , k ] × l r [ j , k ] − l r [ i , k ] × ( l r [ i , k ] + 1 ) 2 ) × u [ i , k ] × d [ j , k ]

考虑使用树状数组来优化。还是一列一列地枚举。
但是在每一列内,需要按照连续的 1 进行分段。
记三个树状数组,在当前枚举到点 (i,j) ( i , j ) 时,存储的是 (i,j) ( i , j ) 在同一段内且横坐标严格大于 i+1 i + 1 的所有点。三个树状数组(树状数组的关键字为 lr[x,y] l r [ x , y ] )分别表示:
第一个树状数组:所有合法点 (x,y) ( x , y ) lr[x,y]×(lr[x,y]1)2×d[x,y] l r [ x , y ] × ( l r [ x , y ] − 1 ) 2 × d [ x , y ] 之和。
第二个树状数组:所有合法点 (x,y) ( x , y ) lr[x,y]×d[x,y] l r [ x , y ] × d [ x , y ] 之和。
第三个树状数组:所有合法点 (x,y) ( x , y ) d[x,y] d [ x , y ] 之和。
注意第一个树状数组维护的是前缀和,第二和第三个树状数组维护的是后缀和。
(以下记 T1(x) T 1 ( x ) 为第一个树状数组以 x x 为结尾的前缀和, T2(x),T3(x) 分别为第二个和第三个树状数组以 x x 为结尾的后缀和)
这样,就只需要枚举双十字的上交点 (i,j)
第一种情况,产生的双十字数目为:
T1(lr[i,j])×u[i,j] T 1 ( l r [ i , j ] ) × u [ i , j ]

第二种情况,产生的双十字数目为:
u[i,j]×(lr[i,j]×T2(lr[i,j]+1)lr[i,j]×(lr[i,j]+1)2×T3(lr[i,j]+1)) u [ i , j ] × ( l r [ i , j ] × T 2 ( l r [ i , j ] + 1 ) − l r [ i , j ] × ( l r [ i , j ] + 1 ) 2 × T 3 ( l r [ i , j ] + 1 ) )

复杂度 O(R×C×logN) O ( R × C × log ⁡ N )
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 1e4 + 5, M = 12e5 + 5, PYZ = 1e9 + 9;
int n, m, T, q, _l[M], _r[M], _u[M], _d[M], orz[M], ans,
T1[N + 1375], T2[N + 1375], T3[N + 1375];
bool a[M];
int which(int i, int j) {return (i - 1) * m + j;}
void change1(int x, int v) {
    for (; x <= 11370; x += x & -x) T1[x] = (T1[x] + v) % PYZ;
}
int ask1(int x) {
    int res = 0; for (; x; x -= x & -x) res = (res + T1[x]) % PYZ; return res;
}
void change2(int x, int v) {
    for (; x <= 11370; x += x & -x) T2[x] = (T2[x] + v) % PYZ;
}
int ask2(int x) {
    int res = 0; for (; x; x -= x & -x) res = (res + T2[x]) % PYZ; return res;
}
void change3(int x, int v) {
    for (; x <= 11370; x += x & -x) T3[x] = (T3[x] + v) % PYZ;
}
int ask3(int x) {
    int res = 0; for (; x; x -= x & -x) res = (res + T3[x]) % PYZ; return res;
}
int main() {
    int i, j, k, nxt; T = (n = read()) * (m = read()); q = read();
    memset(a, true, sizeof(a)); while (q--)
        i = read(), j = read(), a[which(i, j)] = 0;
    For (i, 1, n) For (j, 1, m) {
        int x = which(i, j);
        _l[x] = a[x] ? (j == 1 ? 1 : _l[which(i, j - 1)] + 1) : 0;
        _u[x] = a[x] ? (i == 1 ? 1 : _u[which(i - 1, j)] + 1) : 0;
    }
    Rof (i, n, 1) Rof (j, m, 1) {
        int x = which(i, j);
        _r[x] = a[x] ? (j == m ? 1 : _r[which(i, j + 1)] + 1) : 0;
        _d[x] = a[x] ? (i == n ? 1 : _d[which(i + 1, j)] + 1) : 0;
    }
    For (i, 1, T) orz[i] = min(_l[i], _r[i]) - 1;
    For (j, 1, m) for (i = 1; i <= n;) {
        if (!a[which(i, j)]) {i++; continue;}
        nxt = i; while (nxt <= n && a[which(nxt, j)]) nxt++;
        Rof(k, nxt - 2, i) {
            int x = which(k, j);
            ans = (ans + 1ll * ask1(orz[x]) * (_u[x] - 1) % PYZ) % PYZ;
            ans = (ans + 1ll * ask2(10272 - orz[x]) * orz[x] % PYZ
                * (_u[x] - 1) % PYZ) % PYZ;
            ans = (ans - 1ll * ask3(10272 - orz[x]) *
                (1ll * orz[x] * (orz[x] + 1) >> 1 % PYZ) % PYZ
                * (_u[x] - 1) % PYZ + PYZ) % PYZ;
            int y = which(k + 1, j); if (orz[y]) {
                change1(orz[y], (1ll * orz[y] * (orz[y] - 1) >> 1) % PYZ
                    * (_d[y] - 1) % PYZ);
                change2(10273 - orz[y], 1ll * orz[y] * (_d[y] - 1) % PYZ);
                change3(10273 - orz[y], _d[y] - 1);
            }
        }
        Rof(k, nxt - 1, i + 1) {
            int y = which(k, j); if (orz[y]) {
                change1(orz[y], PYZ - (1ll * orz[y] * (orz[y] - 1) >> 1) % PYZ
                    * (_d[y] - 1) % PYZ);
                change2(10273 - orz[y], PYZ - 1ll * orz[y] * (_d[y] - 1) % PYZ);
                change3(10273 - orz[y], PYZ - _d[y] + 1);
            }
        }
        i = nxt;
    }
    cout << ans << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值