Educational Codeforces Round 8(E. Zbazi in Zeydabad(树状数组优化))

题目链接:点击打开链接

题意:一个n*m矩阵, 里面的格子除了'z'就是'.',问有多少个z形图案。

思路:因为n和m很大, 即使n^3复杂度也会超时。  如果按照最朴素的方法, 我们可以处理一下前缀和, 处理出一个格子向左l[i][j]、向右r[i][j]、斜向左下lr[i][j]连着的z到哪里为止, 这样我们用n^2复杂度枚举每一个格子作为z形图案的右上角,取min(l[i][j], lr[i][j]), 就可以立刻知道这个z形的最左下角到哪里, 然后在这个对角线上扫一遍, 看看向右最远是不是符合条件,复杂度n^3。  我们注意到, 最后一步是处理一个对角线区间上的问题。  这其实可以用树状数组快速累加和。

我们只需要从右向左枚举每一列, 将以这一列为最右端的线段最左端加进树状数组, 那么我们再枚举这一列的所有点作为Z形的右上角, 算出左下角。  由于只有同一对角线上的行加列相同, 所以以此为树状数组编号, 开n + m棵树状数组, 就可以快速累加在这个对角线上的满足列介于上面那一横的范围内的下面那一横的数量了。 又以为从右向左枚举列, 所以先加的一定满足之后的。

细节参见代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<map>
#include<queue>
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
using namespace std;
typedef long long ll;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int mod = 1000000000 + 7;
const int INF = 1000000000;
const int maxn = 3000 + 10;
int T,n,m, bit[maxn*2][maxn], l[maxn][maxn],lr[maxn][maxn],r[maxn][maxn];
ll ans = 0;
struct node {
    int x, y;
};
vector<node> g[maxn];
char s[maxn][maxn];
int sum(int i, int x) {
    int ans = 0;
    while(x > 0) {
        ans += bit[i][x];
        x -= x & -x;
    }
    return ans;
}
void add(int i, int x, int d) {
    while(x <= m) {
        bit[i][x] += d;
        x += x & -x;
    }
}
void pre() {
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=m;j++) {
            if(s[i][j] == 'z') l[i][j] = l[i][j-1]+1;
            else l[i][j] = 0;
        }
        for(int j=m;j>=1;j--) {
            if(s[i][j] == 'z') r[i][j] = r[i][j+1]+1;
            else r[i][j] = 0;
        }
    }
    for(int i=n;i>=1;i--) {
        for(int j=1;j<=m;j++) {
            if(s[i][j] != 'z') { lr[i][j] = 0; continue;}
            if(j-1 >= 1 && i+1 <= n) {
                lr[i][j] = lr[i+1][j-1] + 1;
            }
            else lr[i][j] = 1;
        }
    }
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=m;j++) {
            g[j + r[i][j] - 1].push_back(node{i, j});
        }
    }
}
int main() {
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) {
        scanf("%s",s[i]+1);
    }
    ans = 0;
    pre();
    for(int j=m;j>=1;j--) {
        int len = g[j].size();
        for(int i=0;i<len;i++) {
            int& x = g[j][i].x, &y = g[j][i].y;
            add(x+y, y, 1);
        }
        for(int i=1;i<=n;i++) {
            if(s[i][j] != 'z') continue;
            int len = min(l[i][j], lr[i][j]);
            int y = j - len + 1;
            ans += sum(j+i, j) - sum(i+j, j-len);
        }
    }
    printf("%I64d\n",ans);
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值