编程训练_求和公式

问题描述:

QQ小方以前不会用 excel 里的求和公式,现在他会了,所以他急切的想教会你。
对于 excel 中的一个子矩阵求和的公式格式是 sum(A:B) ,其中 A 表示子矩阵的左上角坐标, B 表示子矩阵的右下角坐标。

  

单单讲给你听肯定是不够的,为了表现自己,QQ小方现在要考考你。
现在QQ小方会给你一个 n×n 的正方形矩阵,并且它会将正方形矩阵的两条对角线位置全部填上 1 (可以参考下图),其余位置全部填上 0 。并且他给正方形矩阵的行和列从 0 到 n−1 标上了序号。
例如下图所示是 4×4 正方形矩阵的填充和行列标号:

  

现在,为了测试你是否真的掌握了 excel 的矩阵求和公式,QQ小方会进行 q 次询问,每次询问一个子矩阵的和。
输入格式
输入第一行包含两个整数 n,q(1≤n≤10^18,1≤q≤10^5) ,分别表示矩阵的大小和询问的次数。
接下来的 q 行,每行包含四个整数 a,b,c,d(0≤a≤c<n,0≤b≤d<n) ,表示子矩阵。其中子矩阵的左上角坐标为 (a,b) ,右下角坐标为 (c,d) 。
输出格式
输出包含 q 行,针对每一个询问输出一个整数表示答案。

样例
input
4 2
0 1 1 3
0 0 3 3
output
3
8

题目求解:

1.题目大意转化为一个矩形包含离散直线 y=x 和 y=n-x-1 点的个数问题。

2.值得注意的是包含的点数目可能为0,且矩阵的范围是10^18,如果使用循环遍历的话,很可能会超时。

3.下面对直线y=x 求包含点的个数,即在X轴上的投影,通过绘图可以得出如下结论

(1). 与 y=x 相交的点有 (a,a),(b,b),(c,c),(d,d),而且落在矩形边界的点在X轴上投影为 max(a,b),min(c,d);

(2). 与 y=x 无交点的判别式:(b-c)*(d-a)>0

   分析:若矩形与直线 y=x 无交点,则矩形处在直线的上方或者下方,即 (b-c)与(d-a)同号,即(b-c)*(d-a)>0;

(3). 对于直线 y=x 求交点个数

// 当在直线 y=x 异侧时有交点
if ((b-c)*(d-a)<=0)
    sum_i = sum(max(a,b),min(c,d));
// 其中sum(int m, int n) 函数的计算方式为
//  abs(n-m)+1;

4.下面对直线y=n-x-1 求包含点的个数,方法同3

(1). 与 y=n-x-1 相交的点有 (a,n-1-a),(b,n-1-b),(n-1-c,c),(d,n-1-d),而且落在矩形边界的点在X轴上投影为 max(b,n-1-c),min(n-1-a,d);

(2). 与 y=n-x-1 无交点的判别式:(a+b+1-n)*(c+d+1-n)>0

   分析:若矩形与直线 y=n-x-1 无交点,则矩形处在直线的上方或者下方,即 (a+b+1-n)与(c+d+1-n)同号,即(a+b+1-n)*(c+d+1-n)>0;

(3). 对于直线 y=n-x-1 求交点个数

// 当在直线 y=n-x-1 异侧时有交点
if ((a+b+1-n)*(c+d+1-n)<=0)
    sum_j = sum(max(b,n-1-c),min(d,n-1-a));

5. 讨论两条直线存在重复值的情况

(1). 由于该直线表达形式为连续直线,则当n为奇数时,会存在中心交点重合的情况;

(2). 此情况下该点有可能计算两次,应该去重

if (n%2 == 1)
    if (a<=n/2 && c>=n/2 && b<=n/2 && d>=n/2)
        flag = 1;	

问题反思:

1.该矩阵的阶次达到 10^18,尽量避免循环遍历;

2.判断矩形是否与直线相交时,应避免整数相乘再判断符号;

// 1. y=x
// 最好不要采用乘积为非正的算式,当数很大的时候会报错 
if (b>=c&&d<=a || b<=c&&d>=a)
	sum_i = sum(max(a,b),min(c,d));
// 2. y=n-x+1 
if (b>=(n-1-a)&&d<=(n-1-c) || b<=(n-1-a)&&d>=(n-1-c))
	sum_j = sum(max(b,n-1-c),min(n-1-a,d));

3.使用求和函数时为了保证结果为正,也应避免使用abs()函数,因为精度会降低,从而导致报错;

long long sum(long long a, long long b)
{
	// printf("abs sum = %lld\n",abs(a-b+1));	
	// printf("sum = %lld\n",(a>b?a-b+1:b-a+1));
       // 上述两式的结果不同
	return a>b?a-b+1:b-a+1;
}

4.不要忘记n为奇数时的去重处理。

if (n%2 == 1)
	if (a<=n/2 && c>=n/2 && b<=n/2 && d>=n/2)
		flag = 1;			
printf("%lld\n",sum_i+sum_j-flag);

AC代码:

# include <stdio.h>
# include <math.h>

long long max(long long a, long long b)
{
	return a>b?a:b;
}

long long min(long long a, long long b)
{
	return a>b?b:a;
}

long long sum(long long a, long long b)
{
	return a>b?a-b+1:b-a+1;
}

int main(void)
{
	long long n, sum_i, sum_j, flag = 0;
	int q;
	long long a, b, c, d;
	scanf("%lld %d",&n, &q); 
	
	while (q>0)
	{
		flag = 0;
		sum_i = sum_j = 0;
		scanf("%lld %lld %lld %lld",&a,&b,&c,&d);
		// 分两条线计算,求交点坐标,然后计算包含的点个数 
		// 1. y=x
		// 最好不要采用乘积为非正的算式,当数很大的时候会报错 
		if (b>=c&&d<=a || b<=c&&d>=a)
			sum_i = sum(max(a,b),min(c,d));
		// 2. y=n-x+1 
		if (b>=(n-1-a)&&d<=(n-1-c) || b<=(n-1-a)&&d>=(n-1-c))
			sum_j = sum(max(b,n-1-c),min(n-1-a,d));
		q--;
		if (n%2 == 1)
			if (a<=n/2 && c>=n/2 && b<=n/2 && d>=n/2)
				flag = 1;			
		printf("%lld\n",sum_i+sum_j-flag);
	}
	
	return 0;
 } 

RRR

posted @ 2019-08-03 17:25  小白的个人总结  阅读(939)  评论(0编辑  收藏  举报