賽后我重拳出擊,賽場上我卻爆零。哎。
題解本人口胡。有錯請各位大佬們指出。
A. 儒略日
這題是大型模擬題。
介紹兩種寫法:一種代碼量致死(賽 場 自 閉),一種是非常好寫的。
寫法 1
我在賽場的思路:預處理三種情況(閏年,平年,鬼畜 \(1582\) 年),然后只需快速找到適當的年就可以了,在 \(1582\) 年前每 \(4\) 年一個循環;\(1582\) 年以后 \(400\) 年一個循環,\(400\) 年中又嵌套着一個 \(100\) 年,\(100\) 年中又前套着 \(4\) 循環。每一個循環的天數相同,可以用整除 \(O(1)\) 快速跳。
時間復雜度
\(O(T)\)。
奉上賽場丑陋的代碼。
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long LL;
LL x;
LL k = 1573, r = 1461, r1 = 1460, d2, d4, d3, d5, d6;
const int N = 405;
int D[3][N], M[3][N];
int inline get(int x, int t) {
if (t && x == 2) return 29;
if (x == 4 || x == 6 || x == 9 || x == 11) return 30;
if (x == 2) return 28;
return 31;
}
int main() {
d4 = 365 * 400 + 97; d5 = 100 * 365 + 25, d6 = 100 * 365 + 24;
for (int i = 0; i <= 1; i++) {
int t = 365 + i;
int d = 0, m = 1;
for (int j = 0; j < t; j++ ){
if (d == get(m, i)) {
m ++, d = 1;
} else {
d++;
}
D[i][j] = d, M[i][j] = m;
}
}
int d = 0, m = 1;
for (int j = 0; j < 365 - 10; j++ ){
if (m == 10 && d == 4) {
d = 15;
} else if (d == get(m, 0)) {
m ++, d = 1;
} else {
d++;
}
D[2][j] = d, M[2][j] = m;
}
d2 = 366 + 365 * 2 - 10; d3 = 17 * 365 + 4;
int T; scanf("%d", &T);
while (T--) {
scanf("%lld", &x);
LL a = min(k, x / r);
LL d = 1, m = 1, y = -4713;
y += a * 4; x -= r * a;
if (y >= 0) y ++;
if (y == 1580 && x >= d2) {
y = 1583; x -= d2;
if (y == 1583 && x >= d3) x -= d3, y = 1600;
if (y == 1583) {
if (x >= 365) {
x -= 365, y++;
}
a = x / r;
y += a * 4; x -= r * a;
int k = (y % 100 || (y % 400 == 0)) ? 366 : 365;
if (x >= k) {
x -= k, y ++;
if (x >= 365) x -= 365, y++;
if (x >= 365) x -= 365, y++;
}
int t = (y % 400 == 0 || (y % 4 == 0 && y % 100));
printf("%d %d %lld\n", D[t][x], M[t][x], y);
continue;
}
a = x / d4;
y += 400 * a; x -= a * d4;
if (x >= d5) {
x -= d5, y += 100;
if (x >= d6) x -= d6, y += 100;
if (x >= d6) x -= d6, y += 100;
}
if (y % 400 != 0) {
if (x >= r1) x -= r1, y += 4;
}
a = x / r;
y += a * 4; x -= r * a;
int k = (y % 100 || (y % 400 == 0)) ? 366 : 365;
if (x >= k) {
x -= k, y ++;
if (x >= 365) x -= 365, y++;
if (x >= 365) x -= 365, y++;
}
int t = (y % 400 == 0 || (y % 4 == 0 && y % 100));
printf("%d %d %lld\n", D[t][x], M[t][x], y);
} else {
if (x >= 366) {
x -= 366, y ++;
if (y == 0) y++;
if (x >= 365) x -= 365, y++;
if (x >= 365) x -= 365, y++;
}
int t;
if (y < 0) t = (y % 4 == -1);
else t = y % 4 == 0;
if (y == 1582) {
printf("%d %d %lld\n", D[2][x], M[2][x], y);
} else {
if (y < 0) printf("%d %d %lld BC\n", D[t][x], M[t][x], -y);
else printf("%d %d %lld\n", D[t][x], M[t][x], y);
}
}
}
return 0;
}
寫法 2
xtq 鴿鴿教了我一個超好寫的方法,tql!
具體是通過觀察樣例 2 發現,到 \(1582\) 年 \(< A = 3000000\) 天,而之后都是 \(400\) 年共 \(97\) 閏年的循環(共 \(T = 365 \times 400 + 97 = 146097\))這個天數的循環。那么暴力預處理出來 \(G = A + T = 3000000 + 146097 = 3146097\) 這么多天的答案,對於任意 \(r\),若 \(r \le A\),直接輸出,若 \(r > M\),答案月日就等同於 \(A + (r - A) \bmod T\),年份就是這個天數的年份 \(+ \lfloor (r - A) / T \rfloor\)。
非 常 好 寫。
時間復雜度
復雜度 \(O(G + T)\),每個詢問還是 \(O(1)\)。
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long LL;
const int N = 3146100;
int A = 3000000, T = 146097, G = A + T;
int D[N], M[N], Y[N];
int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int inline isLeap(int y) {
if (y <= 1582) return y > 0 ? (y % 4 == 0) : (y % 4 == -1);
else return y % 400 == 0 || (y % 4 == 0 && y % 100);
}
int inline getDays(int x, int y) {
if (y && x == 2) return 29;
else return days[x];
}
void init() {
int d = 1, m = 1, y = -4713;
for (int i = 0; i <= G; i++) {
D[i] = d, M[i] = m, Y[i] = y;
if (d == 4 && m == 10 && y == 1582) {
d = 15;
} else if (d == getDays(m, isLeap(y))) {
d = 1;
if (m == 12) {
m = 1, y ++;
if (y == 0) y++;
} else m++;
} else d++;
}
}
void inline out(int d, int m, int y) {
if (y < 0) printf("%d %d %d BC\n", d, m, -y);
else printf("%d %d %d\n", d, m, y);
}
int main() {
init();
int Q; scanf("%d", &Q);
while (Q--) {
LL r; scanf("%lld", &r);
if (r <= A) out(D[r], M[r], Y[r]);
else {
int a = A + ((r - A) % T);
out(D[a], M[a], Y[a] + (r - A) / T * 400);
}
}
return 0;
}
B. 動物園
由於這題保證 \(a_i, q_i\) 各自互不相同,所以會好寫一些。
把所有 \(a_i\) 所覆蓋的所有位搞出來,這只需要一個按位或,設 \(w_i\) 表示所有 \(a\) 中有沒有第 \(i\) 位為 \(1\) 的。
考慮什么情況才不能選擇一位,對於一個要求 \(p, q\),若 \(w[p] = \text{false}\),就意味着第 \(p\) 位肯定不能選(由於 \(q\) 互不相同,不會其他的 \(p\) 會使原來買了 \(q\) 了)。
設能選的位數是 \(k\),答案就是 \(2 ^ k - n\),即所有滿足的(組合計數算出) \(-\) 目前已經有的。
若 \(k = 64\),則式子直接算會爆 \(\text{unsigned long long}\) 需要特判。
時間復雜度
\(O(n)\)。
P.S. 如果不保證各不相同(我賽場就是這樣眼瞎),只需要把 \(q\) 離散化,記錄覆蓋了哪些 \(q\),把 \(a\) 去重即可,再掃一遍限制,復雜度只帶排序的 \(O(n \log n)\)。
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1000005;
typedef unsigned long long ULL;
int n, m, c, K;
bool f[64];
ULL a[N];
int main() {
scanf("%d%d%d%d", &n, &m, &c, &K);
ULL sum = 0;
for (int i = 1; i <= n; i++) {
ULL x; scanf("%llu", &x); sum |= x;
}
int c = K;
for (int i = 1, p, q; i <= m; i++) {
scanf("%d%d", &p, &q);
if (!(sum >> p & 1) && !f[p]) f[p] = true, c--;
}
if (c == 64) {
if (n == 0) puts("18446744073709551616");
else printf("%llu\n", (~0ull) - (n - 1));
} else printf("%llu\n", (1ull << c) - n);
}
C. 函數調用
事實上,你可以把整個問題作如下轉化:
- 對於每個 \(a_i\),可以看作新增了一個在最開始的 \(P = i, V = a_i\) 的 \(1\) 操作。
- 對於函數操作序列,可以看作新增一個 \(3\) 操作 \(m + 1\),他的順序就是 \(f\)。
如果對於每個 \(3\) 操作,讓 \(j\) 向所有他調用的所有函數 \(g\) 順序連一條有向邊,由於題中不會調用自身的限制,這個圖即變成一個有向無環圖。
那么問題就變得統一了起來,即從 \(m + 1\) 開始遍歷,每次按邊的順序 \(dfs\),經過的 \(1, 2\) 操作做一遍操作,求最終的數組。
如果把 \(dfs\) 經過的所有點順序打成一個序列,我們發現乘法實際上是把之前的加法整體乘一個數。
每經過一個加法來說,設其后經過的乘法數值乘積是 \(s\),他的貢獻就是讓 \(a_P\) 加上了 \(V \times s\)。 也可以理解為添加了執行了 \(s\) 次這個操作的貢獻。
我們執行一遍逆拓撲序,把調用每個點走到的所有乘法操作乘積 \(\text{mul}\) 找出來。
我們設 \(cnt_i\) 為第 \(i\) 個點所在操作需要執行多少次,我們只需要把這個東西算出來就可以了。
初始令 \(cnt_{m + 1} = 1\),然后我們嘗試遞推到每一個點。
然后我們執行一遍拓撲排序,對於每個點 \(u\),維護一個值初始為 \(s = cnt_u\) ,我們反向遍歷他的每個指向點 \(v\):
- 讓 \(cnt_v\) 加上 \(s\)
- 讓 \(s\) 為 \(s \times mul_v\)
你可以理解為每次遍歷到 \(u\) 這個點就是需要執行 \(cnt_u\) 次的時候,我們考慮把這個點刪掉,那么對於他的每個指向點 \(v\),只需要加上其后指向邊的乘積次,貢獻加上,那么各個指向點就各自獨立了。由於拓撲排序,因此每個點 \(u\) 被遍歷時,能夠讓 \(cnt\) 變化的點都處理完了,所以是對的。
感覺寫這個的時候好難講清楚...
時間復雜度
\(O(n + m + Q + \sum C_j)\) (線性)
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 100005, P = 998244353;
int n, a[N], m, T[N], B[N], V[N], Q, f[N];
int mul[N], q[N], in[N], deg[N], cnt[N];
vector<int> g[N], e[N];
int inline power(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = (LL)res * a % P;
a = (LL)a * a % P;
b >>= 1;
}
return res;
}
void inline topo1() {
int hh = 0, tt = -1;
for (int i = 1; i <= m; i++) if (!deg[i]) q[++tt] = i;
while (hh <= tt) {
int u = q[hh++];
for (int i = 0; i < e[u].size(); i++) {
int v = e[u][i];
mul[v] = (LL)mul[v] * mul[u] % P;
if (--deg[v] == 0) q[++tt] = v;
}
}
}
void inline topo2() {
int hh = 0, tt = -1;
for (int i = 1; i <= m; i++) if (!in[i]) q[++tt] = i;
while (hh <= tt) {
int u = q[hh++], s = cnt[u];
for (int i = g[u].size() - 1; ~i; i--) {
int v = g[u][i];
(cnt[v] += s) %= P;
if (--in[v] == 0) q[++tt] = v;
s = (LL)s * mul[v] % P;
}
}
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", a + i);
scanf("%d", &m);
for (int i = 1; i <= m; i++) mul[i] = 1;
for (int i = 1; i <= m; i++) {
scanf("%d", T + i);
if (T[i] == 1) scanf("%d%d", B + i, V + i);
else if (T[i] == 2) scanf("%d", V + i), mul[i] = V[i];
else if (T[i] == 3) {
scanf("%d", V + i);
for (int j = 0, x; j < V[i]; j++) {
scanf("%d", &x);
g[i].push_back(x), e[x].push_back(i);
in[x]++, deg[i]++;
}
}
}
topo1();
scanf("%d", &Q);
for (int i = 1; i <= Q; i++) scanf("%d", f + i);
int s = 1;
for (int i = Q; i; i--) {
if (T[f[i]] != 2) (cnt[f[i]] += s) %= P;
s = (LL)s * mul[f[i]] % P;
}
for (int i = 1; i <= n; i++) a[i] = (LL)a[i] * s % P;
topo2();
for (int i = 1; i <= m; i++)
if (T[i] == 1) a[B[i]] = (a[B[i]] + (LL)V[i] * cnt[i]) % P;
for (int i = 1; i <= n; i++) printf("%d ", a[i]);
return 0;
}
D. 貪吃蛇
假設每條蛇都選擇吃,我們把每輪的情況模擬出來,即模擬出:
- \(mx_i / mn_i\) 還剩 \(i\) 條蛇的輪次(吃之前)時,實力最強 / 最弱的蛇
- \(d_i\) 第 \(i\) 條蛇被吃掉后,還有幾條蛇
由於輪次越前的蛇越有決定權,我們可以考慮從后往前推,最終結果僅有一條蛇,這是這條蛇滿意的結果,動態維護一個 \(ans = 1\),然后枚舉 \(i\) 從 \(2\) 到 \(n\) (枚舉還有幾條蛇的時間):
- 如果 \(d_{mx_i} \ge ans\),那么 \(mx_i\) 會在從當前狀態到我們目前答案的時間段中被吃掉,所以 \(mx_i\) 這輪不會選擇吃,即要改變 \(ans = i\)。
- 否則,\(mx_i\) 會同意吃,所以 \(ans\) 不變,因為自己在后續不會死。
由於慘淡的數據范圍,要求我們必須做到線性 \(O(Tn)\)
后面推的時間是線性的,只需要快速(線性)模擬情況。
把問題抽象出來:
開始我們有 \(a_1 \le a_2 \le \text{...} < a_n\),\(n\) 個數,每個數與其下標作為一個二元組參與比較 \((a_i, i)\),把這些二元組加入集合。
- 執行 \(n - 1\) 輪,每次從集合中找出並彈出最大和最小的兩個數 (設這兩個數下標為 \(mx, mn\)),然后把 \((a_{mx} - a_{mn}, mx)\) 加入集合。
如果要線性,必須滿足加入的這個東西滿足一些單調性,如 NOIP 2016 蚯蚓的那題。
我們設第 \(i\) 輪最大最小的數值分別是 \(A_i, B_i\),推入集合的數值是 \(C_i = A_i - B_i\)。
因為不會推入更大的數進集合,所以滿足 \(A_i \ge A_{i+1}\),此時只需滿足 \(B_i \le B_{i+1}\),我們就可以發現 \(C_i\) 是單調非嚴格遞減的了。但實際並不令我們所想的那樣美好,我們需要對 \(B\) 的情況進行分類討論。
Case 1
若需 \(B_i \le B_{i+1}\),需要做到推入的數不小於最小的數,即對於任意的 \(i\) 都有 \(A_i - B_i \ge B_i \Leftrightarrow A_i \ge 2B_i\) 的話,\(C_i\) 是單調非嚴格遞減的。那么我們只需要像蚯蚓那題一樣開兩個單調數組,\(b\) 保存初始的蚯蚓,\(c\) 保存推入的蚯蚓,兩個蚯蚓都是單調的,極值就在隊頭隊尾。
還有一個小問題,我們只保證數值滿足關系,下標能否滿足呢?
當 \(C_i = C_{i+1}\) 時,當且僅當 \(A_i = A_{i+1}\) 且 \(B_i = B_{i+1}\),在這種情況下由於 \(A_i\) 的下標是大於 \(A_{i+1}\) 的下標,所以產生的 \(C_i\) 下標也是遞減的。
Case 2
我們找到第一個 \(A_i - B_i < B_i \Leftrightarrow A_i < 2B_i\)。
設此時有 \(n\) 個元素,然后按順序最小到大排好是 \(a\) 數組(實際上是把兩個 Case 分開處理了,現在變成了一個新的問題而且初始就滿足)。
此時排序,實際上是一個將 Case 1 中兩個單調數組做二路歸並排序的過程,是 \(O(n)\) 的,並且只會執行一次。
那么 \(A_i = a_n, B_i = a_1\)。
這種時候:
- 第 \(2\) 輪取出來的最小元素肯定是上次的數即 \(a_n - a_1\),最大值是 \(a_{n - 1}\),產生的 \(a_{n - 1} - (a_n - a_1) = (a_{n - 1} - a_n) + a_1 \le a_1\)
- 第 \(3\) 輪取出來的最小元素的值(注意這里元素不一定,因為還有下標的限制)一定是 \(a_{n - 1} - (a_n - a_1)\),最大元素一定是 \(a_{n - 2}\),產生的 \(a_{n - 2} - [a_{n - 1} - (a_n - a_1)] = a_{n - 2} - a_{n - 1} + (a_n - a_1) < a_1\)
注意這里的輪重新從進入 Case 2 算起,可以理解為我們把問題變成了一個一開始就滿足 \(a_n < 2a_1\) 的一個問題。
這種時候,通過觀察發現后續偶數輪數值都是 \(\le a_1\),奇數輪都是 \(< a_1\)。
還是數學歸納法證一下吧:
設 \(f_i\) 為第 \(i\) 輪推出的數,\(f_1 = a_n - a_1 < a_1, f_2 = a_{n - 1} - (a_n - a_1) = (a_{n - 1} - a_n) + a_1 \le a_1\)
對於任意 \(i \ge 3\),有 \(f_i = a_{n - i + 1} - f_{i - 1} = a_{n - i + 1} - a_{n - i + 2} + f_{i - 2} \le f_{i - 2}\),因此每個 \(i\) 都有 \(f_i \le f_{i \bmod 2}\)
證畢。
這樣證明完以后,我們發現產生的數要么馬上下一輪作為最小值被咔嚓,要么有跟他同值得倒霉蛋代替他受罰。
那如果此時我們還用 Case 1 的方法還能保持單調嗎?發現同值情況僅出現在 \(= a_1\) 的情況。如果推入的數 \(\not= a_1\),他下一輪就被取出去,所以可以保證 \(c\) 只有一個元素。若有多個 \(= a_1\) 的情況,即需要滿足當前的 \([n - i + 1, n]\) 這個區間的 \(a\) 全 \(= a_n\),才能滿足那個偶數輪次取等。\(b\) 隊列中相同的值,下標大的會先作為最大值然后彈出來,所以 \(c\) 中也是滿足單調遞減的。
時間復雜度
\(O(Tn)\)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
using namespace std;
const int N = 1000005, INF = 2e9;
int n, a[N], d[N], mx[N], mn[N], ans;
struct E{
int x, id;
bool operator < (const E &b) const {
if (x != b.x) return x < b.x;
return id < b.id;
}
bool operator == (const E &b) const {
return x == b.x && id == b.id;
}
};
E b[N], c[N], f[N];
int hh, tt, L, R;
E inline getMax() {
E x = (E) { -1, -1 };
if (hh <= tt && x < b[hh]) x = b[hh];
if (L <= R && x < c[L]) x = c[L];
if (hh <= tt && x == b[hh]) hh++;
if (L <= R && x == c[L]) L++;
return x;
}
E inline getMin() {
E x = (E) { INF, INF };
if (hh <= tt && b[tt] < x) x = b[tt];
if (L <= R && c[R] < x) x = c[R];
if (hh <= tt && x == b[tt]) tt--;
if (L <= R && x == c[R]) R--;
return x;
}
void inline merge() {
int len = tt - hh + 1 + R - L + 1;
for (int k = 1, i = hh, j = L; k <= len; k++) {
if (j > R || (i <= tt && c[j] < b[i])) f[k] = b[i++];
else f[k] = c[j++];
}
L = 0, R = -1;
hh = 1, tt = len;
for (int i = 1; i <= len; i++) b[i] = f[i];
}
void inline solve() {
hh = 0, tt = -1, L = 0, R = -1;
memset(d, 0, sizeof d);
ans = 1;
for (int i = n; i; i--) b[++tt] = (E) { a[i], i };
bool ok = false;
for (int i = n; i >= 2; i--) {
E A = getMax(), B = getMin();
mx[i] = A.id, mn[i] = B.id;
c[++R] = (E) { A.x - B.x, mx[i] };
if (!ok && A.x < B.x * 2) {
ok = true;
merge();
}
d[mn[i]] = i - 1;
}
for (int i = 2; i <= n; i++)
if (d[mx[i]] >= ans) ans = i;
printf("%d\n", ans);
}
int main() {
int T; scanf("%d", &T); T--;
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", a + i);
solve();
while (T--) {
int k; scanf("%d", &k);
for (int i = 1, x, y; i <= k; i++) {
scanf("%d%d", &x, &y);
a[x] = y;
}
solve();
}
return 0;
}