普及-每日一題洛谷P1147
題目描述
對一個給定的正整數 \(M\),求出所有的連續的正整數段(每一段至少有兩個數),這些連續的自然數段中的全部數之和為 \(M\)。
例子:\(1998+1999+2000+2001+2002 = 10000\),所以從 \(1998\) 到 \(2002\) 的一個自然數段為 \(M=10000\) 的一個解。
輸入格式
包含一個整數的單獨一行給出 \(M\) 的值(\(10 \le M \le 2,000,000\))。
輸出格式
每行兩個正整數,給出一個滿足條件的連續正整數段中的第一個數和最後一個數,兩數之間用一個空格隔開,所有輸出行的第一個按從小到大的升序排列,對於給定的輸入資料,保證至少有一個解。
樣例輸入
10000
樣例輸出
18 142
297 328
388 412
1998 2002
可以採取列舉、字首和、二分、雙指標等方法
列舉+二分解出區間跨度:
思路大致如下:
-
首先處理資料範圍,減少複雜度:
因為(\(10 \le M \le 2,000,000)\),並且每一段至少有兩個數,所以能保證取到 \(M=2,000,000\) 的最小區間為\([(1,000,000),(1,000,001)]\)
-
意思就是說我們只需要從 \(0\) 遍歷到 \(M/2\),對每次遍歷都做一次二分搜尋,沒有搜尋到匹配的區間跨度則退出繼續遍歷
-
當某一次遍歷找到了匹配的區間跨度使得區間和為 \(M\) 時,輸出當前遍歷的左區間和二分找到的右區間
-
直到遍歷結束,退出程式
具體實現如下:
find
函式,尋找匹配的區間跨度:
int find(int start) {
int l = 1, r = n;
while (l<r)
{
long long mid = l + r >> 1;//long long 防止求和資料溢位
if ((start + mid + start) * (mid + 1) / 2 == n)//等差數列求和公式,二分結果可能過大
return mid;
else if ((start + mid + start) * (mid + 1) / 2 > n)
r = mid;
else
l = mid + 1;
}
return 0;//未找到匹配的區間跨度返回0
}
從 \(1\) 開始遍歷到 \(M/2\):
for (int i = 1; i <= m / 2; i++) {
if (find(i) == 0) continue;//未找到匹配的區間跨度則繼續遍歷i
else printf("%d %d\n", i, i + find(i));//輸出匹配的區間跨度
}
完整程式碼:
int m;
int find(int start) {
int l = 1, r = m;
while (l<r)
{
long long mid = l + r >> 1;
if ((start + mid + start) * (mid + 1) / 2 == m)
return mid;
else if ((start + mid + start) * (mid + 1) / 2 > m)
r = mid;
else
l = mid + 1;
}
return 0;
}
int main()
{
scanf("%d", &m);
for (int i = 1; i <= m / 2; i++) {
if (find(i) == 0) continue;
else printf("%d %d\n", i, i + find(i));
}
return 0;
}
列舉+字首和找到區間:
思路如下:
- 儲存字首和到陣列中,同理只需要存到第 \(1,000,000\) 項
- 遍歷右區間,在存在右區間的前提下,往回檢索左區間,存在匹配的左右區間,直接輸出
- 因為每次遍歷左區間時,區間和總是隨左區間減小而增大的,如果遍歷到的左區間使區間和大於 \(M\),則直接退出
- 若遍歷完左區間,未能找到匹配值,則繼續遍歷右區間
記錄字首和:
for (int i = 1; i <= 1000000; i++)
sum[i] += sum[i - 1] + i;
遍歷右左區間:
for (int i = 1; i <= m / 2 + 1; i++) {//先遍歷右區間
for (int j = i; j >= 1; j--) {//往回檢索左區間
if (sum[i] - sum[j - 1] > m) break;//區間和大於M直接退出
if (sum[i] - sum[j - 1] == m && j != i) printf("%d %d\n", j, i);
}
}
完整程式碼:
int m,sum[1000010];
int main()
{
scanf("%d", &m);
for (int i = 1; i <= 1000000; i++)
sum[i] += sum[i - 1] + i;
for (int i = 1; i <= m / 2 + 1; i++) {
for (int j = i; j >= 1; j--) {
if (sum[i] - sum[j - 1] > m) break;
if (sum[i] - sum[j - 1] == m && j != i) printf("%d %d\n", j, i);
}
}
return 0;
}