20241030每日一題洛谷P1147

才瓯發表於2024-11-02

普及-每日一題洛谷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;
}

相關文章