zoukankan      html  css  js  c++  java
  • 多维前缀和

    问题描述

    二维平面上,给定N(大约200000)个点,这些点的x和y的取值范围都是[1,3000]之间的整数,给定M(大约200000)个查询,每个查询输入一个点P(px,py)。对于每个查询,求N个点到点P的距离之和。

    输入

    第一行两个整数N和M
    接下来N行表示N个点的x和y坐标
    接下来M行表示M个查询的x和y坐标

    3 3
    5 5
    5 10 
    10 5
    1 1
    5 5 
    10 10
    

    输出

    M个正整数,每个查询输出一个正整数

    思路

    二维前缀和

    定义二维数组xsum[3001][3001],xsum[x,y]表示区域[0,x],[0,y]上x坐标之和。这样一来,任意区域[fx,tx],[fy,ty]的x坐标之和就可以表示为xsum[fy,ty]-xsum[fx,ty]-xsum[tx,fy]+xsum[fx,tx]。

    对于每个查询的点P,只需要处理它的左上方、左下方、右上方、右下方四个区域中的距离之和(方向使用二维空间直角坐标系)。

    例如,点P左上方的点数为K,点P左上方的x坐标之和、y坐标之和分别为xs,ys,则点P左上方的点到P的距离为:K*px-xs+ys-K*py

    代码

    #include<stdio.h>
    #include<iostream> 
    using namespace std;
    const int maxn = 200007;
    const int ma = 3003;
    int a[ma][ma];
    int xsum[ma][ma], ysum[ma][ma];
    int c[ma][ma];
    //前开后闭区间
    int getxsum(int fx, int fy, int tx, int ty) {//前开后闭区间
    	return xsum[tx][ty] - xsum[fx][ty] - xsum[tx][fy] + xsum[fx][fy];
    }
    int getysum(int fx, int fy, int tx, int ty) {
    	return ysum[tx][ty] - ysum[fx][ty] - ysum[tx][fy] + ysum[fx][fy];
    }
    int getcnt(int fx, int fy, int tx, int ty) { 
    	return c[tx][ty] - c[tx][fy] - c[fx][ty] + c[fx][fy];
    } 
    int main() {
    	freopen("in.txt", "r", stdin); 
    	int N, M;
    	cin >> N >> M;
    	memset(a, 0, sizeof(a));
    	memset(xsum, 0, sizeof(xsum));
    	memset(ysum, 0, sizeof(ysum));
    	memset(c, 0, sizeof(c));
    	for (int i = 0; i < N; i++) {
    		int x, y;
    		cin >> x >> y;
    		a[x][y]++;
    	}
    	for (int x = 1; x < ma; x++) {
    		for (int y = 1; y < ma; y++) {
    			xsum[x][y] = a[x][y] * x + xsum[x][y - 1]+xsum[x-1][y]-xsum[x-1][y-1];
    			ysum[x][y] = a[x][y] * y + ysum[x][y - 1]+ysum[x-1][y]-ysum[x-1][y-1];
    			c[x][y] = a[x][y]-c[x-1][y-1]+c[x-1][y]+c[x][y-1];
    		}
    	}  
    	for (int i = 0; i < M; i++) {
    		int x, y;
    		cin >> x >> y; 
    		int s = 0;
    		int cnt = getcnt(0, 0, x , y ); 
    		s += cnt * x + cnt * y - getxsum(0, 0, x , y) - getysum(0, 0, x, y );
    		cnt = getcnt(x , 0, ma-1, y ); 
    		s += getxsum(x, 0, ma-1, y) - cnt*x + cnt * y - getysum(x , 0, ma-1, y );
    		cnt = getcnt(0, y , x, ma-1); 
    		s += getysum(0, y, x, ma-1) - y * cnt + cnt * x - getxsum(0, y, x, ma-1);
    		cnt = getcnt(x , y , ma-1, ma-1); 
    		s += getxsum(x , y , ma-1, ma-1) + getysum(x , y , ma-1, ma-1) - cnt * x - cnt * y;
    		cout << s << endl;
    	}
    	return 0;
    }
    
    

    总结

    多维前缀和的计算方式可以认为是容斥原理。对于三维前缀和,加减号可以直接通过二进制表示的奇偶性来表示。

    下面来段代码测试一下这个思路

    import java.util.Random;
    
    public class Main {
    class Point {
        int x, y, z;
    
        Point(int x, int y, int z) {
            this.x = x;
            this.y = y;
            this.z = z;
        }
    
        @Override
        public String toString() {
            return String.format("(%d,%d,%d)", x, y, z);
        }
    }
    
    final int N = 100;//100*100*100的三维空间内
    final int POINT_COUNT = 4000;//点的个数
    final int QUESTION_COUNT = 4000;//问题的个数
    
    Random r = new Random(0);
    
    //随机一个点,点的各个维度取值范围要在1到N-1之间
    Point randomPoint() {
        return new Point(r.nextInt(N - 2) + 1, r.nextInt(N - 2) + 1, r.nextInt(N - 2) + 1);
    }
    
    //生成问题
    Point[] generateProblem() {
        Point[] a = new Point[POINT_COUNT];
        for (int i = 0; i < a.length; i++) {
            a[i] = randomPoint();
        }
        return a;
    }
    
    //绝对正确的方法
    class Stupid {
        Point[] a;
    
        Stupid(Point[] a) {
            this.a = a;
        }
    
        int solve(Point fp, Point tp) {
            int s = 0;
            for (Point i : a) {
                if (i.x >= fp.x && i.y >= fp.y && i.z >= fp.z && i.x <= tp.x && i.y <= tp.y && i.z <= tp.z) {
                    s++;
                }
            }
            return s;
        }
    }
    
    //快速方法
    class Fast {
        int[][][] a, c;
    
        Fast(Point[] p) {
            //a[i,j,k]表示i,j,k处的点的个数
            a = new int[N][N][N];
            //c[i,j,k]表示000到ijk处的点的总数
            c = new int[N][N][N];
            for (Point i : p) {
                a[i.x][i.y][i.z]++;
            }
            for (int i = 1; i < N; i++) {
                for (int j = 1; j < N; j++) {
                    for (int k = 1; k < N; k++) {
                        c[i][j][k] = c[i - 1][j][k] + c[i][j - 1][k] + c[i][j][k - 1] - c[i - 1][j - 1][k] - c[i - 1][j][k - 1] - c[i][j - 1][k - 1] + c[i - 1][j - 1][k - 1] - a[i][j][k];
                    }
                }
            }
        }
    
        int solve(Point fp, Point tp) {
            int s = 0;
            for (int i = 0; i < 8; i++) {
                //i各个bit
                int one = i & 1, two = (i >> 1) & 1, three = (i >> 2) & 1;
                int x = tp.x, y = tp.y, z = tp.z;
                //符号位
                int sgn = (one ^ two ^ three) == 0 ? -1 : 1;
                if (one != 0) x = fp.x - 1;
                if (two != 0) y = fp.y - 1;
                if (three != 0) z = fp.z - 1;
                s += c[x][y][z] * sgn;
            }
            return s;
        }
    }
    
    Main() {
        Point[] p = generateProblem();
        Stupid stupid = new Stupid(p);
        Fast fast = new Fast(p);
        for (int i = 0; i < QUESTION_COUNT; i++) {
            //生成一对点作为查询区间,起始点的各个坐标必须小于终结点的各个坐标
            Point fp = randomPoint(), tp = randomPoint();
            if (fp.x > tp.x) {
                int temp = fp.x;
                fp.x = tp.x;
                tp.x = temp;
            }
            if (fp.y > tp.y) {
                int temp = fp.y;
                fp.y = tp.y;
                tp.y = temp;
            }
            if (fp.z > tp.z) {
                int temp = fp.z;
                fp.z = tp.z;
                tp.z = temp;
            }
            int realAns = stupid.solve(fp, tp);
            int mine = fast.solve(fp, tp);
            if (realAns != mine) {
                throw new RuntimeException("error on from=" + fp + ",to=" + tp + " " + realAns + " " + mine);
            }
        }
    }
    
    public static void main(String[] args) {
        new Main();
    }
    }
    
  • 相关阅读:
    Android Toast效果
    禁锢的思维
    偶遇makeblock
    发疯,宁愿一年少赚10万也不想和芯片打交道了
    Linux物理内存相关数据结构
    字符设备驱动框架
    Linux在IA-32体系结构下的地址映射
    使用busybox构建根文件系统
    在内容长页处动态增加滚动的返回头部图标
    用IE滤镜实现多种常用的CSS3效果
  • 原文地址:https://www.cnblogs.com/weiyinfu/p/9672652.html
Copyright © 2011-2022 走看看