1. 程式人生 > 程式設計 >Nginx配置實現下載檔案的示例程式碼

Nginx配置實現下載檔案的示例程式碼

題目來源:Codeforces, CF1406E Deleting Numbers

題目大意

互動題。

有一個未知的整數 \(x\) (\(1\leq x\leq n\)),你需要通過互動找出它。

初始時,互動庫會告訴你 \(n\)

有一個集合 \(\{1,2,\dots ,n\}\)。你可以進行不超過 \(10^4\) 次操作。每次操作可以是如下三種之一:

  • \(\text{A }a\):向互動庫詢問,當前集合裡有多少數是 \(a\) 的倍數。
  • \(\text{B }a\):向互動庫詢問,當前集合裡有多少數是 \(a\) 的倍數。然後將所有 \(a\) 的倍數刪去。特別地:\(x\) 將不會被刪去,即使它是 \(a\)
    的倍數。此操作必須保證 \(a > 1\)
  • \(\text{C }a\):向互動庫報告你找出的 \(x\)

請在規定操作次數內求出 \(x\)

資料範圍:\(1\leq n\leq 10^5\)

本題題解

考慮一種暴力做法。列舉 \(1\dots n\) 中每個質數 \(p\),再列舉 \(p\)\(n\) 以下的所有次冪 \(p^k\)。先用 \(\text{B}\) 操作刪除 \(p^k\) 的倍數,再用 \(\text{A}\) 操作查詢集合裡是否仍有 \(p^k\) 的倍數。若有,說明 \(x\)\(p^k\) 的倍數。這樣,我們可以知道每個質數在 \(x\)

裡的最大次冪是多少,根據唯一分解定理,我們也就確定了 \(x\)

\(\pi(n)\)\(1\dots n\) 中的質數數量,\(\tau(n)\)\(1\dots n\) 中的質數的冪的數量。打表發現,\(\pi(10^5)=9592,\tau(10^5)=9700\)。上述做法,需要 \(2\tau(n)+1\) 次操作(最後一次操作是報告答案),無法通過本題。

一個小優化是,對每個質數 \(p\),先用 \(\text{B}\) 操作刪除所有 \(p\) 的倍數,再依次詢問所有次冪。操作次數優化為 \(\pi(n)+\tau(n)+1\),但仍無法通過本題。

有一個經典的技巧是,把質數分為 \(\leq \sqrt{n}\)

的“小質數”和 \(> \sqrt{n}\) 的“大質數”。小質數總共只有 \(\pi(\sqrt{n})\) 個。而大質數在任何 \(x\) 裡至多隻有 \(1\) 個。

先用 \(\pi(\sqrt{n})+\tau(\sqrt{n})\leq 238\) 次操作,求出 \(x\) 中所有小質因子及其次冪。設 \(x\) 的小質因子部分為 \(x_{\text{small}}\)。此時,集合裡還剩下 \(1\)\(x\)、以及所有大質數。我們要確定 \(x\) 裡還剩下的那 \(1\) 個大質因子是誰(或者根本沒有大質因子)。

在前面的樸素做法裡,我們實際對每個大質數用了 \(2\) 次詢問,這樣太多了。考慮減小這個次數。分兩種情況:

  • 如果 \(x_{\text{small}} > 1\):我們對每個大質數 \(p\),直接用 \(\text{A}\) 操作詢問 \(p\times x_{\text{small}}\)。如果結果為 \(1\),說明 \(x\) 就等於 \(p\times x_{\text{small}}\)(不然的話,這個數此時應該已經被刪了);如果結果為 \(0\),說明 \(p\) 不是 \(x\) 的大質因子。這樣,對每個大質因子只需要 \(1\) 次操作。找出大質因子所需的操作次數等於大質因子的數量,為:\(\pi(n)-\pi(\sqrt{n})\)。加上小質數的部分,以及報告答案的操作,總次數為 \(\pi(\sqrt{n})+\tau(\sqrt{n})+(\pi(n)-\pi(\sqrt{n}))+1=\pi(n)+\tau(\sqrt{n})+1\leq 9766\)
  • 如果 \(x_{\text{small}}=1\):此時 \(p\times x_{\text{small}}\) 就是大質數 \(p\) 本身,它本來就應該在集合裡,所以直接詢問是沒有意義的。這種情況下,集合裡只剩 \(1\) 和大質數(因為 \(x\) 本身也是 \(1\) 或大質數)。考慮大質數序列分塊。每塊裡,先依次用 \(\text{B}\) 操作將本塊的大質數全部刪除,然後用 \(\text{A}\) 操作對 \(1\) 進行一次詢問(也就是詢問整個集合剩餘的數的數量)。如果發現集合大小減少的量,少於我們刪除的量,說明 \(x\) 就在本塊裡,我們列舉本塊中的數,依次詢問。這樣,設塊的大小為 \(S\),則找出大質數所需的操作次數為:\(\pi(n)-\pi(\sqrt{n})+\lceil\frac{\pi(n)-\pi(\sqrt{n})}{B}\rceil+B\)。取 \(B = \lfloor\sqrt{\pi(n)-\pi(\sqrt{n})}\rfloor = 97\) 時,這部分的最大操作次數為 \(9723\)。加上小質數的部分,以及報告答案的操作,最大總次數為 \(9962\)。可以通過本題。

參考程式碼

// problem: CF1406E
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }

const int MAXN = 1e5;
const int SQRT = 316;
const int BLOCK_SIZE = 97;

int ask(int x) {
	cout << "A " << x << endl;
	int res; cin >> res;
	return res;
}
int ask_and_del(int x) {
	cout << "B " << x << endl;
	int res; cin >> res;
	return res;
}
void report_ans(int x) {
	cout << "C " << x << endl;
}

int n, ans;
int p[MAXN + 5], cnt_p;
bool v[MAXN + 5];
void sieve(int lim) {
	for (int i = 2; i <= lim; ++i) {
		if (!v[i]) {
			p[++cnt_p] = i;
		}
		for (int j = 1; j <= cnt_p && p[j] * i <= lim; ++j) {
			v[i * p[j]] = 1;
			if (i % p[j] == 0) break;
		}
	}
}
int main() {
	cin >> n;
	sieve(n);
	ans = 1;
	// first part: 
	int second_part_start = cnt_p + 1;
	for (int i = 1; i <= cnt_p; ++i) {
		if (p[i] > SQRT) {
			second_part_start = i;
			break;
		}
		int res = ask_and_del(p[i]);
		if (!res) continue;
		int lastval = 0, flag = 0;
		for (ll j = p[i]; j <= n; j *= p[i]) {
			if (!ask(j)) {
				ans *= j / p[i];
				flag = 1;
				break;
			}
			lastval = j;
		}
		if (!flag) {
			ans *= lastval;
		}
	} // 238
	
	// second part:
	if (ans > 1) {
		for (int i = second_part_start; i <= cnt_p; ++i) {
			if ((ll)p[i] * ans > n)
				continue;
			if (ask(p[i] * ans)) {
				ans *= p[i];
				break;
			}
		} // 9765
		report_ans(ans);
	} else {
		// 分塊
		for (int i = second_part_start; i <= cnt_p; i += BLOCK_SIZE) {
			int j = min(cnt_p, i + BLOCK_SIZE - 1);
			for (int k = i; k <= j; ++k) {
				ask_and_del(p[k]);
			}
			int res = ask(1);
			if (res > 1 + cnt_p - j) {
				assert(res == 1 + cnt_p - j + 1);
				for (int k = i; k <= j; ++k) {
					if (ask(p[k])) {
						ans = p[k];
						break;
					}
				}
				break;
			}
		} // 9961
		report_ans(ans);
	}
	return 0;
}