代码源div1每日一题“Z”型矩阵

题目“Z”型矩阵

这题真是阴间啊,听dls听半天不知道怎么维护这个信息,现在大概记录一下我想清楚这个题的心路历程

考虑最暴力的方法,对于每个左上角 ( X , Y ) (X,Y) (X,Y)计算一个Z形矩阵,那么可以再枚举一下Z的右下角那个点,可以发现这个做法是 O ( n 3 ) O(n^3) O(n3)的。需要用树状数组优化一下。

对于每个左上角 ( X , Y ) (X,Y) (X,Y),考虑其向左以及向左下角延伸的最大长度,记为L,那么可以发现,我们需要数的点都会落在 y i ∈ [ y , y − l + 1 ] y_i\in[y, y-l+1] yi[y,yl+1]这根斜线上。所以,对于每一根斜线,都需要建立一个树状数组。再发现每根斜线的 x + y x+y x+y都是确定的,所以可以开一个二维数组来维护。

现在每左上角顶点需要查询的区间以及树状数组都确定了,那么要怎么插入呢?

记点 ( i , j ) (i,j) (i,j)向右延伸的z长度为 r r r, 那么我们需要统计的是 j + r − 1 > = y j+r-1>=y j+r1>=y那些点,所以我们可以从大到小插入,如果我们想计算点 ( x , y ) (x,y) (x,y),那么我们必须先插入那些 j + r − 1 > = y j+r-1>=y j+r1>=y的点,所以可以先从最右边的点开始计算。

代码

#include <bits/stdc++.h>
#define x first
#define y second
#define mp make_tuple
using namespace std;
const int N = 3e3 + 10, M = 1e6 + 10, mod = 1e9 + 7, INF = 1e9 + 10;
typedef long long LL;
typedef pair<int,int> PII;
using tp = tuple<int,int,int> ;
bool multi = false;

char s[N][N];
int n, m;
int lst[N][N], rst[N][N], dia[N][N];

struct Fenwick {
	int c[N], n;
	inline void Init(int _n) {
		n = _n;
		for (int i = 1; i <= n; ++ i) c[i] = 0;
	}
	inline void Add(int x, int v) {
		for (; x <= n; x += x & -x) c[x] += v;
	}
	inline int Ask(int x) {
		int sm = 0;
		while(x) {
            sm += c[x];
            x &= x - 1;
        }
		return sm;
	}
    inline int Sum(int l, int r) {
        return Ask(r) - Ask(l - 1);
    }
} bit[N << 1];

//保存可以ri, 可以延伸最右边的点(i,j)
vector<tp> rid;

void solve() {
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%s", s[i] + 1);


    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++)
            if(s[i][j] == 'z') lst[i][j] = lst[i][j - 1] + 1;
            else lst[i][j] = 0;
        
        for(int j = m; j; j--)
            if(s[i][j] == 'z') rst[i][j] = rst[i][j + 1] + 1;
            else rst[i][j] = 0;
        
        for(int j = 1; j <= m; j++) {
            if(s[i][j] == 'z') {
                rid.push_back({j + rst[i][j] - 1, i, j});
            }
        }
    }

    for(int i = n; i;  i--) {
        for(int j = 1; j <= m; j++)
            if(s[i][j] == 'z') dia[i][j] = dia[i + 1][j - 1] + 1;
            else dia[i][j] = 0;
    }

    for(int i = 1; i <= n + m; i++) bit[i].Init(m);
    
    sort(rid.begin(), rid.end());
    int k = rid.size() - 1;
    LL res = 0;
    //从最右边的点开始插入
    for(int j = m; j; j--) {
        while(k >= 0 && get<0>(rid[k]) >= j) {
            auto[rr, x, y] = rid[k--];
            bit[x + y].Add(y, 1);           //插入y或者x都行,查询稍微不一样
        }

        for(int i = 1; i <= n; i++) {
            if(s[i][j] != 'z') continue;
            int L = min(lst[i][j], dia[i][j]);
            res += bit[i + j].Sum(j - L + 1, j);
        }
    }
    cout << res << endl;

}
int main()
{
#ifdef ONLINE_JUDGE
#else 
    freopen("C.txt", "r", stdin);
#endif
//     ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T = 1;
    if(multi) cin >> T;
    while (T--) solve();
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值