問題描述:函數
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 點的個數問題。spa
2.值得注意的是包含的點數目可能爲0,且矩陣的範圍是10^18,若是使用循環遍歷的話,極可能會超時。excel
3.下面對直線y=x 求包含點的個數,即在X軸上的投影,經過繪圖能夠得出以下結論blog
(1). 與 y=x 相交的點有 (a,a),(b,b),(c,c),(d,d),並且落在矩形邊界的點在X軸上投影爲 max(a,b),min(c,d);input
(2). 與 y=x 無交點的判別式:(b-c)*(d-a)>0io
分析:若矩形與直線 y=x 無交點,則矩形處在直線的上方或者下方,即 (b-c)與(d-a)同號,即(b-c)*(d-a)>0;class
(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