A. Turtle and Piggy Are Playing a Game
首先\(p\)選\(2\)的話除得最慢,得的分多。考慮二進位制表示,如果\(x = (1000000000)_{bin}\),則每次除以\(2\)都是相當於右移一位,除完之後仍然是\(2\)的倍數,變成\(1\)的步數就是把最高位的\(1\)移動到\(0\)位的步數。
因為\(2l \le r\),所以\(l \le \lfloor \frac{r}{2} \rfloor\),若\(r = (1011101)_{bin}\),則\(r >> 1 = (101110)_{bin}\),可以發現\((1000000)_{bin}\)一定是在\([l, r]\)區間內部,因此我們可以直接選擇\(x = 1 << (lg(r))\)。
int l, r;
void solve() {
cin >> l >> r;
cout << lg(r) << '\n';
}
B. Turtle and an Infinite Sequence
寫下幾組資料發現第\(m\)次改變後\(a_n\)的值變為\([n - m, n + m]\)這個區間內所有\(a_i\)的按位或的值。
令\(l = n - m\),\(r = n + m\)。
l: 01011 0 11011
r: 01011 1 00110
設從左到右第一個不相等的位為\(bit\),則\(ans\)的\(bit\)位左邊的值就是\(l\)或\(r\)的這個位的值。對\(bit\)位及其右邊的位,發現01011 0 11111
在\([l, r]\)內,因此後面的位每一位或出來都是\(1\),則加上\((1 << (bit + 1)) - 1\)即可。
int n, m;
void solve() {
cin >> n >> m;
int l = max(0, n - m);
int r = n + m;
int ans = 0;
for (int i = 31; i >= 0; i --) {
if ((l >> i & 1) != (r >> i & 1)) {
ans += (1 << (i + 1)) - 1;
break;
}
else {
ans |= (l >> i & 1) << i;
}
}
cout << ans << '\n';
}
C. Turtle and an Incomplete Sequence
考慮所有非\(-1\)的位置,我們要保證這些位置中每相鄰的兩個位置上的數要在他們的間隔允許的步數範圍內實現相互轉化。
比如\(3, -1, -1, -1, 9, -1\),我們要保證\(3\)可以在\(4\)步之內轉化到\(9\),透過\(/2\)或者\(*2\)或者\(*2+1\)的方式。
既然如此,我們可以考慮最少需要多少步,可以將\(a\)轉化成\(b\)。
3: 0011
9: 1001
首先我們可以把\(3\)的字首一給刪掉。
3: 1 1
9: 1 0 01
然後,這些操作可以抽象為二進位制序列右移一位、左移一位補\(0\)、左移一位補\(1\)三種。
因為操作都是從尾部進行的,所以\(3\)和\(9\)的公共字首部分可以不用考慮,從第一個不相同的位開始後面的所有位都會被操作(需要或者不需要修改的位都會在修改者第一個不相同的位的時候被順便操作到),所以這個最小步數就是\(3\)和\(9\)的第一個不相同的位及其後面的位數之和的和。
因為從\(a\)變成\(b\),位數的變化是一定的,二每個操作都是增加一個位或者減少一個位,所以所有操作方案都和最小方案具有相同的奇偶性。
因此我們只要判斷每兩個非\(-1\)位置的間距是否大於等於最小方案並且與其具有相同奇偶性即可。
對於第一個非\(-1\)位置的左邊和最後一個非\(-1\)位置的右邊,我們連續地\(*2\),\(/2\)即可。
int n;
int a[M];
int b[M];
int get_times_l(int x, int y) {
int l = lg(x), r = lg(y);
int i, j;
for (i = l, j = r; i >= 0 && j >= 0; i --, j --) {
if ((x >> i & 1) != (y >> j & 1)) {
return i + 1;
}
}
return i + 1;
}
int get_times_r(int x, int y) {
int l = lg(x), r = lg(y);
int i, j;
for (i = l, j = r; i >= 0 && j >= 0; i --, j --) {
if ((x >> i & 1) != (y >> j & 1)) {
return j + 1;
}
}
return j + 1;
}
void solve() {
cin >> n;
for (int i = 1; i <= n; i ++) {
b[i] = 1;
}
for (int i = 1; i <= n; i ++) {
cin >> a[i];
}
for (int i = 1; i <= n; i ++) {
if (a[i] != -1) {
b[i] = a[i];
int j;
for (j = i + 1; j <= n; j ++) {
if (a[j] != -1) {
break;
}
}
if (j > n) {
break;
}
int l = get_times_l(a[i], a[j]);
int r = get_times_r(a[i], a[j]);
// cout << l << ' ' << r << '\n';
// cout << i << ' ' << j << '\n';
if (j - i >= l + r && (j - i - l - r) % 2 == 0) {
for (int k = i + 1; k <= i + l; k ++) {
b[k] = b[k - 1] / 2;
}
int rr = r;
for (int k = i + l + 1; k <= i + l + r; k ++) {
b[k] = b[k - 1] * 2 + ((a[j] >> (rr-- - 1)) & 1);
}
int cnt = 0;
for (int k = i + l + r + 1; k < j; k ++) {
if (cnt++ & 1) b[k] = b[k - 1] / 2;
else b[k] = b[k - 1] * 2;
}
}
else {
cout << "-1\n";
return;
}
i = j - 1;
}
}
int pos_l = -1, pos_r = -1;
for (int i = 1; i <= n; i ++) {
if (a[i] != -1) {
pos_l = i;
break;
}
}
for (int i = n; i >= 1; i --) {
if (a[i] != -1) {
pos_r = i;
break;
}
}
int cnt = 0;
if (pos_l != -1) {
for (int i = pos_l - 1; i >= 1; i --) {
if (cnt++ & 1) b[i] = b[i + 1] / 2;
else b[i] = b[i + 1] * 2;
}
}
cnt = 0;
if (pos_r != -1) {
for (int i = pos_r + 1; i <= n; i ++) {
if (cnt++ & 1) b[i] = b[i - 1] / 2;
else b[i] = b[i - 1] * 2;
}
}
if (pos_l == -1 && pos_r == -1) {
b[1] = 1;
cnt = 0;
for (int i = 2; i <= n; i ++) {
if (cnt++ & 1) b[i] = b[i - 1] / 2;
else b[i] = b[i - 1] * 2;
}
}
for (int i = 1; i <= n; i ++) {
cout << b[i] << ' ';
}
cout << '\n';
}
D. Turtle and Multiplication
\(a_i \cdot a_{i+1} = a_j \cdot a_{j+1}\)的必要條件是無序對\((a_i, a_{i+1})\)和無序對\(a_j, a_{j+1}\)相同。實際上,當\(a_i\)都是質數時,這個必要條件會變成充要條件。如果兩個質數對中有一個質數是相同的,那麼乘積相同則另一個質數也是相同的;如果兩個質數對中的質數兩兩不同,由分解質因數可知他們的乘積為\(2^a 3^b 5^c ...\),其中\(a, b, c, ...\)中只能有且只有兩個為1,其他全為0,那麼排列組合的話也就只有這一種組合,故無序對相同是充要條件。
我們可以把\((a_i, a_{i+1})\)看作是一條邊,則問題變為找到點數最少得無向完全圖(每個點還有一個自環),是的這個完全圖存在一條經過\(n - 1\)條邊且不經過重複邊的路徑。
考慮若完全圖點數確定,我們如何計算這個完全圖的最長不經過重複邊的路徑長度。
設完全圖點數為\(m\),若\(m\)是奇數則每個點的度數都是偶數,所以這個圖存在尤拉路徑,路徑長度等於邊數等於\(\frac{m(m+1)}{2}\)。
若\(m\)是偶數那麼每個點的度數都是奇數,我們需要刪除一些邊使得這個圖存在尤拉路徑。可以發現刪掉一條邊最多可使奇度數的點的數量減少\(2\),所以我們至少需要刪除\(\frac{m}{2} - 1\)條邊。發現有一種方案:刪除\((2, 3), (4, 5), ..., (m - 2, m - 1)\)這些邊即可。路徑長度為\(\frac{m(m-1)}{2} - \frac{m}{2} + 1 + m = \frac{m^2}{2} + 1\)。
當\(n = 10^6\)時最小的\(m\)是\(1415\),第\(1415\)小的質數是\(11807\),符合\(a_i \le 3 \cdot 10^5\)。
我們可以二分求出最小的\(m\),再用Fleury等演算法求出一個無向圖的尤拉路徑。
時間複雜度:每個測試樣例\(O(n)\)。
注意,我們在新增尤拉回路的路徑時,只把當前邊的\(v\)節點(這個變到達的那個點)加到了棧中,所以最後要記得再把\(1\)號節點(初始的起點)也加到棧中。陣列大小的\(3N\)是為了防止越界所設,實際上接近\(2N\)即可。
int n;
int h[N], e[N * 3], ne[N * 3], idx;
int st[N], primes[N], cnt;
int used[N * 3], tot;
int ans[N * 3];
void getPrimes(int n) {
for (int i = 2; i <= n; i ++) {
if (!st[i]) primes[cnt ++] = i;
for (int j = 0; primes[j] <= n / i; j ++) {
st[primes[j] * i] = 1;
if (i % primes[j] == 0) break;
}
}
}
void addEdge(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
bool check(int x) {
if (x & 1) return x * (x + 1) / 2 >= n - 1;
else return x * x / 2 + 1 >= n - 1;
}
void dfs(int u) {
for (int i = h[u]; ~i; i = h[u]) {
if (used[i]) {
h[u] = ne[i];
continue;
}
used[i] = 1;
used[i ^ 1] = 1;
h[u] = ne[i];
dfs(e[i]);
ans[++tot] = e[i];
}
}
void solve() {
cin >> n;
memset(h, -1, (n + 2) * 4);
idx = 0;
tot = 0;
int l = 1, r = 10000;
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
int m = l;
for (int i = 1; i <= m; i ++) {
for (int j = i; j <= m; j ++) {
if (!(m & 1) && j == i + 1 && !(i & 1))
continue;
addEdge(i, j);
addEdge(j, i);
}
}
for (int i = 0; i < idx; i ++) {
used[i] = 0;
}
dfs(1);
ans[++tot] = 1;
reverse(ans + 1, ans + 1 + tot);
// for (int i = 1; i <= tot; i ++) {
// cout << ans[i] << " \n"[i == tot];
// }
for (int i = 1; i <= n; i ++) {
cout << primes[ans[i] - 1] << " \n"[i == n];
}
}
bool Med;
int main() {
fprintf(stderr, "%.3lf MB\n", (&Med - &Mbe) / 1048576.0);
// setIO();
int T = 1;
cin >> T;
getPrimes(1e6);
// for (int i = 0; i < 100; i ++) {
// cout << primes[i] << " \n"[i == 99];
// }
while (T --) solve();
cerr << 1e3 * clock() / CLOCKS_PER_SEC << " ms\n";
return 0;
}