BZOJ1807 [Ioi2007]Pairs 彼此能听得见的动物对数 [树状数组, 曼哈顿转切比雪夫]

P a i r s Pairs Pairs

题目描述见链接 .


正 解 部 分 \color{red}{正解部分}

第一个子任务额外开一个指针即可解决问题, 这里不再多说 .


然后解决第二个子任务:

首先要知道 曼哈顿距离切比雪夫距离,
这里补充以下内容,

切比雪夫距离: d = max ⁡ ( ∣ x i − x j ∣ ) d = \max(|x_i-x_j|) d=max(xixj)

( x 1 , y 1 ) (x_1, y_1) (x1,y1) ( x 2 , y 2 ) (x_2, y_2) (x2,y2)曼哈顿距离 d d d, 则

d = ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣    = max ⁡ ( x 1 − x 2 + y 1 − y 2 ,   x 2 − x 1 + y 1 − y 2 ,   x 1 − x 2 + y 2 − y 1 ,   x 2 − x 1 − y 2 + y 1 )    = max ⁡ ( ∣ ( x 1 + y 1 ) − ( x 2 + y 2 ) ∣ , ∣ ( x 1 − y 1 ) − ( x 2 − y 2 ) ∣ ) \begin{aligned} & d = |x_1-x_2| + |y_1-y_2|\\ & \ \ = \max(x_1-x_2+y_1-y_2,\ x_2-x_1+y_1-y_2, \ x_1-x_2+y_2-y_1,\ x_2-x_1-y_2+y_1)\\ & \ \ = \max(|(x_1+y_1)- (x_2+y_2)|, |(x_1-y_1) -(x_2-y_2)|)\\ \end{aligned} d=x1x2+y1y2  =max(x1x2+y1y2, x2x1+y1y2, x1x2+y2y1, x2x1y2+y1)  =max((x1+y1)(x2+y2),(x1y1)(x2y2))

由此得出 d d d 同样可以表示为 ( x 1 + y 1 , x 1 − y 1 ) (x_1+y_1, x_1-y_1) (x1+y1,x1y1) ( x 2 + y 2 , x 2 − y 2 ) (x_2+y_2, x_2-y_2) (x2+y2,x2y2) 之间的 切比雪夫距离 .


将所有坐标 ( x i , y i ) (x_i, y_i) (xi,yi) 转化为 ( x i + y i , x i − y i ) (x_i+y_i, x_i-y_i) (xi+yi,xiyi) 后,
( x 1 , y 1 ) (x_1, y_1) (x1,y1) ( x 2 , y 2 ) (x_2, y_2) (x2,y2) 能够互相听见的条件变为了 max ⁡ ( ∣ x 2 − x 1 ∣ , ∣ y 2 − y 1 ∣ ) ≤ D \max(|x_2-x_1|, |y_2-y_1|) \le D max(x2x1,y2y1)D ,
于是把坐标按 x x x 坐标排序, 维护一个类似 子任务一 的指针保证 ∣ x 2 − x 1 ∣ ≤ D |x_2-x_1| \le D x2x1D,
然后使用 以 y y y 为下标的 树状数组 去查询 [ y i − D , y i + D ] [y_i-D,y_i+D] [yiD,yi+D] 内的点数 即为 i i i 对答案的贡献 .


现在来讨论第三个子任务怎么解决,

同样将 x , y x,y x,y 两维放到 切比雪夫 坐标系中, z z z 仍然在 曼哈顿 坐标系中,
由于 M ≤ 75 M \le 75 M75, 所以每个面可以维护一个前缀和 s u m [ i , j ] sum[i, j] sum[i,j] 表示这个面 ( i , j ) (i, j) (i,j) 点左下方的动物数量,
z z z 为第一关键字, x x x 为第二关键字, y y y 为第三关键字 从小到大 排序, 然后遍历,
设现在要计算点 ( x 1 , y 1 , z 1 ) (x_1, y_1, z_1) (x1,y1,z1) 对答案的贡献, 首先枚举 z z z z 2 z_2 z2 的面, 其中 z 2 ≤ z 1 z_2 \le z_1 z2z1,
然后求这个面中满足条件: max ⁡ ( ∣ x 1 − x 2 ∣ , ∣ y 1 − y 2 ∣ ) + z 1 − z 2 ≤ D \max(|x_1-x_2|, |y_1-y_2|) + z_1-z_2 \le D max(x1x2,y1y2)+z1z2D 的点数,
化简得 max ⁡ ( ∣ x 1 − x 2 ∣ , ∣ y 1 − y 2 ∣ )   ≤   D − ( z 1 − z 2 ) \max(|x_1-x_2|,|y_1-y_2|)\ \le\ D - (z_1-z_2) max(x1x2,y1y2)  D(z1z2), 设 d = D − ( z 1 − z 2 ) d = D - (z_1 - z_2) d=D(z1z2),
取出 x x x 进行化简, ∣ x 1 − x 2 ∣ ≤ d |x_1-x_2| \le d x1x2d, 得 x 1 − x 2 ≤ d x_1-x_2 \le d x1x2d 或者 x 2 − x 1 ≤ d x_2 - x_1 \le d x2x1d,
y y y 的化简同理,
最后只需要在 ( [ x − d , x + d ] , [ y − d , y + d ] ) ([x-d,x+d], [y-d,y+d]) ([xd,x+d],[yd,y+d]) 使用前缀和查询即可 .


实 现 部 分 \color{red}{实现部分}

  • 曼哈顿切比雪夫 y y y 坐标可能出现负数, 要注意加上一个数 .
  • 注意 第三个子任务 统计答案时 当 z 2 = z 1 z_2 = z_1 z2=z1 ( x , y ) (x, y) (x,y) 会计算 2 2 2 次, 因此同一个面的答案要除 2 2 2 .
  • 子任务一的指针 初值必须为 1 1 1, 不能为 0 0 0 .
  • 子任务三 中 前缀和数组 y y y维 的大小原应该开 3 M 3M 3M , 由于下标右移数组大小需要增大 M M M, 总共需要开 4 M 4M 4M .
    但是为了保险还是尽量开 理论上的 2 2 2 倍吧 …
  • 这道题目卡空间, 开数组要尽量节制 …

#include<bits/stdc++.h>
#define reg register
typedef long long ll;

int read(){
        char c;
        int s = 0, flag = 1;
        while((c=getchar()) && !isdigit(c))
                if(c == '-'){ flag = -1, c = getchar(); break ; }
        while(isdigit(c)) s = s*10 + c-'0', c = getchar();
        return s * flag;
}

const int maxn = 1e5 + 5;

int N;
int D;
int M;

ll Ans;

struct Node{ int x, y, z, id; } A[maxn];

bool cmp_1(Node a, Node b){ return a.x==b.x?(a.y==b.y?a.z<b.z:a.y<b.y):a.x<b.x; }

namespace Task_1{ 
        void Solve_1(){ 
                N = read(), D = read(), M = read(); 
                for(reg int i = 1; i <= N; i ++) A[i].x = read(); 
                std::sort(A+1, A+N+1, cmp_1); 
                int t = 1; 
                for(reg int i = 1; i <= N; i ++){ 
                        while(t < i && A[i].x - A[t].x > D) t ++; 
                        Ans += i - t; 
                } 
                printf("%lld\n", Ans); 
        }
}

namespace Task_2{

        struct Bit_Tree{
                int v[200005];
                void Add(int k, int x){ while(k<=M)v[k]+=x,k+=k&-k; }
                int Query(int k){ if(k<=0) return 0; int s=0; while(k)s+=v[k],k-=k&-k; return s; }
        } bit_t;

        void Solve_2(){
                N = read(), D = read(), M = read();
                for(reg int i = 1; i <= N; i ++){
                        A[i].x = read(), A[i].y = read();
                        int tmp = A[i].x;
                        A[i].x = A[i].x + A[i].y;
                        A[i].y = tmp - A[i].y + M;
                }
                std::sort(A+1, A+N+1, cmp_1);
                int t = 0; M <<= 1;
                for(reg int i = 1; i <= N; i ++){
                        while(t < i && A[i].x-A[t].x > D){ if(t != 0) bit_t.Add(A[t].y, -1); t ++; }
                        int l = std::max(0, A[i].y-D), r = std::min(M, A[i].y+D);
                        int res = bit_t.Query(r) - bit_t.Query(l-1);
                        Ans += res;
                        bit_t.Add(A[i].y, 1);
                }
                printf("%lld\n", Ans);
        }
}

namespace Task_3{

        ll Tmp_1;

        int sum[80][355][505];

        bool cmp_2(Node a, Node b){ return a.z==b.z?(a.x==b.x?a.x<b.x:a.y<b.y):a.z<b.z; }

        void Solve_3(){
                N = read(), D = read(), M = read();
                D = std::min(D, 4*M);
                for(reg int i = 1; i <= N; i ++){
                        A[i].x = read(), A[i].y = read(), A[i].z = read();
                        int tmp = A[i].x;
                        A[i].x = A[i].x + A[i].y, A[i].y = tmp - A[i].y;
                        sum[A[i].z][A[i].x][A[i].y+M] ++, Tmp_1 --;
                }
                for(reg int i = 1; i < 80; i ++)
                        for(reg int j = 1; j < 355; j ++)
                                for(reg int k = -M+1; k < 400; k ++)
                                        sum[i][j][k+M] += sum[i][j-1][k+M] + sum[i][j][k-1+M] - sum[i][j-1][k-1+M];
                std::sort(A+1, A+N+1, cmp_2);
                for(reg int i = 1; i <= N; i ++){
                        for(reg int j = 1; j < A[i].z; j ++){
                                int new_d = D - (A[i].z - j);
                                if(new_d < 0) continue ;
                                int x2 = std::max(1, A[i].x-new_d), y2 = std::max(1, A[i].y-new_d+M);
                                Ans += sum[j][A[i].x+new_d][A[i].y+new_d+M] - sum[j][x2-1][A[i].y+new_d+M] - sum[j][A[i].x+new_d][y2-1] + sum[j][x2-1][y2-1];
                        } 
                        int j = A[i].z;
                        int new_d = D - (A[i].z - j); 
                        if(new_d < 0) continue ; 
                        int x2 = std::max(1, A[i].x-new_d), y2 = std::max(1, A[i].y-new_d+M); 
                        Tmp_1 += sum[j][A[i].x+new_d][A[i].y+new_d+M] - sum[j][x2-1][A[i].y+new_d+M] - sum[j][A[i].x+new_d][y2-1] + sum[j][x2-1][y2-1];
                }
                Ans += Tmp_1 >> 1;
                printf("%lld\n", Ans);
        }
}

int main(){
        int Type = read();
        if(Type == 1) Task_1::Solve_1();
        else if(Type == 2) Task_2::Solve_2();
        else Task_3::Solve_3();
        return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值