[BZOJ1201][HNOI2005]数三角形(树状数组)

下面定义 (i,j) ( i , j ) 表示第 i i 行的第j顶点(共 n+1 n + 1 行,第 i i 行有i个顶点)
考虑一个朴素的做法:
一、利用递推,预处理出 5 5 个值:
(1)lef[i][j] (i,j) ( i , j ) 通过未被删除的边,向左延伸的最长距离,如样例中 lef[3][3]=0 l e f [ 3 ] [ 3 ] = 0 lef[4][4]=3 l e f [ 4 ] [ 4 ] = 3
(2) leup[i][j] l e u p [ i ] [ j ] (i,j) ( i , j ) 通过未被删除的边,向左上延伸的最长距离。
(3) riup[i][j] r i u p [ i ] [ j ] (i,j) ( i , j ) 通过未被删除的边,向右上延伸的最长距离。
(4) ledw[i][j] l e d w [ i ] [ j ] (i,j) ( i , j ) 通过未被删除的边,向左下延伸的最长距离。
(5) ridw[i][j] r i d w [ i ] [ j ] (i,j) ( i , j ) 通过未被删除的边,向右下延伸的最长距离。
统计正立三角形时,先枚举右下顶点 (i,j) ( i , j ) 。容易得出,
右下顶点为 (i,j) ( i , j ) 的正立三角形个数不超过 min(lef[i][j],leup[i][j]) min ( l e f [ i ] [ j ] , l e u p [ i ] [ j ] )
k=jmin(lef[i][j],leup[i][j]) k = j − min ( l e f [ i ] [ j ] , l e u p [ i ] [ j ] ) ,那么第 i i 行的第[k,j1]个顶点都有可能成为正立三角形的左下角。
而对于任何的 h[k,j1] h ∈ [ k , j − 1 ] (i,h) ( i , h ) 向右上延伸后,能与 (i,j) ( i , j ) 构成正立三角形的充分必要条件是:

riup[i][h]jh r i u p [ i ] [ h ] ≥ j − h

统计倒立三角形也一样,先枚举右上顶点 (i,j) ( i , j )
右上顶点为 (i,j) ( i , j ) 的正立三角形个数不超过 min(lef[i][j],ledw[i][j]) min ( l e f [ i ] [ j ] , l e d w [ i ] [ j ] )
k=jmin(lef[i][j],ledw[i][j]) k = j − min ( l e f [ i ] [ j ] , l e d w [ i ] [ j ] ) ,那么第 i i 行的第[k,j1]个顶点都有可能成为倒立三角形的左上角。
对于任何的 h[k,j1] h ∈ [ k , j − 1 ] (i,h) ( i , h ) 向右下延伸后,能与 (i,j) ( i , j ) 构成倒立三角形的充分必要条件是:
ridw[i][h]jh r i d w [ i ] [ h ] ≥ j − h

复杂度是 O(n3) O ( n 3 ) 的。考虑将上面给出的两个不等式移项:
riup[i][h]+hj r i u p [ i ] [ h ] + h ≥ j

ridw[i][j]+hj r i d w [ i ] [ j ] + h ≥ j

这样就转换成了求一个区间内,有多少个数大于一个定值。将询问离线排序之后,可以使用树状数组解决。复杂度 O(n2logn) O ( n 2 log ⁡ n )
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
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 = 1024, M = 4096;
int n, led[N][N], rid[N][N], lef[N][N], leu[N][N], riu[N][N], T[M], tot; ll ans;
void change(int x, int v) {
    for (; x <= 4000; x += x & -x) T[x] += v;
}
int ask(int x) {
    int res = 0; for (; x; x -= x & -x) res += T[x]; return res;
}
struct cyx {
    int id, ri;
    cyx() {}
    cyx(int _id, int _ri) : id(_id), ri(_ri) {}
} otz[M];
inline bool comp(const cyx &a, const cyx &b) {
    return a.ri > b.ri;
}
int main() {
    int i, j, x, y, z; n = read();
    for (i = 1; i <= n; i++) for (j = 1; j <= i; j++) {
        x = read(); y = read(); z = read();
        lef[i + 1][j + 1] = z; led[i][j] = riu[i + 1][j] = x;
        rid[i][j] = leu[i + 1][j + 1] = y;
    }
    n++; for (i = 1; i <= n; i++) for (j = 1; j <= i; j++)
        lef[i][j] = lef[i][j] ? lef[i][j - 1] + 1 : 0;
    for (j = 1; j <= n; j++) for (i = j; i <= n; i++) {
        leu[i][j] = leu[i][j] ? leu[i - 1][j - 1] + 1 : 0;
        riu[i][j] = riu[i][j] ? riu[i - 1][j] + 1 : 0;
    }
    for (i = n; i; i--) for (j = 1; j <= i; j++) {
        led[i][j] = led[i][j] ? led[i + 1][j] + 1 : 0;
        rid[i][j] = rid[i][j] ? rid[i + 1][j + 1] + 1 : 0;
    }
    for (i = 1; i <= n; i++) for (j = 1; j <= i; j++)
        riu[i][j] += j, rid[i][j] += j;
    for (i = 1; i <= n; i++) {
        for (j = 0; j <= 4000; j++) T[j] = 0; tot = 0;
        for (j = 1; j <= i; j++) otz[++tot] = cyx(j, riu[i][j]);
        sort(otz + 1, otz + tot + 1, comp); int orz = 1;
        for (j = i; j; j--) {
            while (orz <= tot && otz[orz].ri >= j)
                change(otz[orz].id, 1), orz++;
            ans += ask(j - 1) - ask(j - min(lef[i][j], leu[i][j]) - 1);
        }
        for (j = 0; j <= 4000; j++) T[j] = 0; tot = 0;
        for (j = 1; j <= i; j++) otz[++tot] = cyx(j, rid[i][j]);
        sort(otz + 1, otz + tot + 1, comp); orz = 1;
        for (j = i; j; j--) {
            while (orz <= tot && otz[orz].ri >= j)
                change(otz[orz].id, 1), orz++;
            ans += ask(j - 1) - ask(j - min(lef[i][j], led[i][j]) - 1);
        }
    }
    cout << ans << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值