[考試總結]ZROI-21-CSP7連-DAY1 總結
估計是溫水煮青蛙,本場比賽比較水。
#T1
#Problem
一種遞迴定義的數列(字串列?):第一項為 1
,後面的每一項形式為上一項自左往右 相鄰的相同數字個數+該數字
,比如第二項為 11
,表示 1
個 1
,問第 \(n(n\leq 25)\) 項。
#Solution
就是簡單模擬,沒有難度。
#Code
#include <bits/stdc++.h> #define ll long long #define mset(l, x) memset(l, x, sizeof(l)) using namespace std; const int N = 100010; const int INF = 0x3fffffff; int n, t[N][2], cnt, len, tlen, lst; int main() { scanf("%d", &n); t[len = 1][1] = 1; for (int i = 2; i <= n; ++ i) { tlen = 0, lst = 0, cnt = 0; for (int j = 1; j <= len; ++ j) { if (j != 1 && t[j][(i - 1) % 2] != lst) { t[++ tlen][i % 2] = cnt; t[++ tlen][i % 2] = lst; cnt = 0; } lst = t[j][(i - 1) % 2]; ++ cnt; } t[++ tlen][i % 2] = cnt; t[++ tlen][i % 2] = lst; len = tlen; } for (int i = 1; i <= len; ++ i) printf("%d", t[i][n % 2]); return 0; }
#T2
#Problem
一個嚴格遞增的序列 \(a_i\),支援兩種操作:
- 區間修改
- 查詢是否有位置滿足 \(a_i=i\)
#Solution
我們來考慮所有滿足 \(a_i=i\) 的位置中 \(i\) 最大的,不難發現如果存在這樣的位置,那麼其一定具有二分性,即設該位置為 \(i\),那麼對於所有的 \(j<i\),有 \(a_j\leq j\),對於 \(j>i\),則有 \(a_j > i\),於是我們可以依靠此性質進行二分,注意我們需要判斷二分結束後的位置是否滿足條件(賽時腦抽忘判了 \(100pts\to 10pts\) QwQ),如果不滿足,那麼意味著不存在這樣的位置,同時需要注意邊界。
關於區間修改,我們可以直接採用線段樹維護,由於線段樹本身便是天然的二分結構,所以二分的過程可以直接線上段樹上進行。總體時間複雜度為 \(O(k\log n).\)
#Code
#include <bits/stdc++.h> #define ll long long #define mset(l, x) memset(l, x, sizeof(l)) using namespace std; const int N = 20000010; const int INF = 0x3fffffff; template <typename T> inline void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } struct Node {int ls, rs, mx, tag;} p[N]; int cnt, n, m, a[N], rt; inline void add(int k, int x) { p[k].tag += x; p[k].mx += x; } inline void pushdown(int k) { int ls = p[k].ls, rs = p[k].rs; if (ls) add(ls, p[k].tag); if (rs) add(rs, p[k].tag); p[k].tag = 0; } inline void pushup(int k) { p[k].mx = p[p[k].rs].mx; } void build(int &k, int l, int r) { if (!k) k = ++ cnt; int mid = l + r >> 1; if (l == r) {p[k].mx = a[l]; return;} build(p[k].ls, l, mid); build(p[k].rs, mid + 1, r); pushup(k); } void modify(int k, int l, int r, int x, int y, int c) { if (x <= l && r <= y) {add(k, c); return;} int mid = l + r >> 1; pushdown(k); if (x <= mid) modify(p[k].ls, l, mid, x, y, c); if (mid < y) modify(p[k].rs, mid + 1, r, x, y, c); pushup(k); } int get_pos(int k, int l, int r) { if (l == r) {return p[k].mx == l ? l : 0;} pushdown(k); int mid = l + r >> 1; int lval = p[p[k].ls].mx; if (lval > mid) return get_pos(p[k].ls, l, mid); if (lval < mid) return get_pos(p[k].rs, mid + 1, r); return mid; } int main() { read(m); read(n); for (int i = 1; i <= n; ++ i) read(a[i]); a[n + 1] = n + 1; build(rt, 0, n + 1); -- m; int pos1 = get_pos(rt, 0, n + 1); if (pos1 < 1 || pos1 > n) printf("NO\n"); else printf("YES\n"); while (m --) { int l, r, c; read(l); read(r); read(c); modify(rt, 0, n + 1, l, r, c); int pos = get_pos(rt, 0, n + 1); if (pos < 1 || pos > n) printf("NO\n"); else printf("YES\n"); } return 0; }
#T3
#Problem
給定序列 \(a_i\),\(q\) 個詢問,每次詢問位於 \(a_l\) 到 \(a_r\) 這個子序列中出現次數為奇數的數的個數。
#Solution
莫隊的板子題,不多講。
#Code
#include <bits/stdc++.h>
#define ll long long
#define mset(l, x) memset(l, x, sizeof(l))
using namespace std;
const int N = 500010;
const int INF = 0x3fffffff;
int n, m, a[N], t[N], cnt[N];
int len, res, ans[N];
struct Query {
int l, r, id;
inline bool operator < (const Query &b) const {
if (l / len != b.l / len) return l < b.l;
if ((l / len) & 1) return r < b.r;
return r > b.r;
}
};
Query q[N];
inline void add(int x) {
++ cnt[x]; if (cnt[x] & 1) ++ res; else -- res;
}
inline void del(int x) {
-- cnt[x]; if (cnt[x] & 1) ++ res; else -- res;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++ i)
scanf("%d", &t[i]), a[i] = t[i];
sort(t + 1, t + n + 1);
int _n = unique(t + 1, t + n + 1) - t - 1;
for (int i = 1; i <= n; ++ i)
a[i] = lower_bound(t + 1, t + _n + 1, a[i]) - t;
scanf("%d", &m);
for (int i = 1; i <= m; ++ i)
scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
len = sqrt(n); sort(q + 1, q + m + 1);
for (int i = 1, l = 1, r = 0; i <= m; ++ i) {
while (l > q[i].l) add(a[-- l]);
while (r < q[i].r) add(a[++ r]);
while (l < q[i].l) del(a[l ++]);
while (r > q[i].r) del(a[r --]);
ans[q[i].id] = res;
}
for (int i = 1; i <= m; ++ i)
printf("%d\n", ans[i]);
return 0;
}
#T4
本題是 TJOI 的一道原題 [題目連結]
#Problem
求 \(n\) 個點的二叉樹的葉結點期望個數,對 \(2148473647\) 取模。
用 \(2148473647\) 做模數就噁心人,最開始以為是 \(2147483647(2^{31}-1)\),發現這樣沒有逆元...
#Solution
設 \(f_n\) 表示 \(n\) 個節點的不同二叉樹的個數,\(g_n\) 表示 \(n\) 個節點的 \(f_n\) 個二叉樹的葉結點總數。答案顯然應當是
\[\dfrac{g_n}{f_n} \]\(f_n\) 顯而易見是 Catalan 數,那麼來考慮 \(g_n\) 怎麼求,通過打表可以得到如下性質
\[g_n=nf_{n-1} \]我們這樣考慮證明:
- 對於每棵 \(n\) 個點 \(k\) 個葉結點的二叉樹,如果我們把這 \(k\) 個葉結點分別去掉,可以得到 \(k\) 棵不同的 \(n-1\) 個節點的二叉樹;
- 對於每棵 \(n-1\) 個點的二叉樹,我們知道有 \(n\) 個位置可以掛上一個葉結點,所以通過上面的變換,每一棵 \(n-1\) 個點的二叉樹可以被得到 \(n\) 次;
於是綜合上面兩點不難得到 \(g_n=nf_{n-1}\) 的結論,於是有
\[\dfrac{g_n}{f_n}=\dfrac{nf_{n-1}}{f_n} \]其中有 \(f_n=\dfrac{\binom{2n}{n}}{n+1}\),帶入化簡後可以得到
\[\dfrac{g_n}{f_n}=\dfrac{nf_{n-1}}{f_n}=\dfrac{n(n+1)}{2(2n-1)}. \]#Code
#include <bits/stdc++.h>
#define ll unsigned long long
#define mset(l, x) memset(l, x, sizeof(l))
using namespace std;
const ll N = 100010;
const ll INF = 0x3fffffff;
const ll MOD = 2148473647;
ll n;
inline ll fpow(ll a, ll b) {
ll res = 1;
while (b) {
if (b & 1) (res *= a) %= MOD;
(a *= a) %= MOD; b >>= 1;
}
return res;
}
int main() {
scanf("%llu", &n);
ll res1 = n * (n + 1) % MOD;
ll res2 = fpow(2 * (2 * n - 1) % MOD, MOD - 2);
printf("%llu", (res1 * res2 % MOD + MOD) % MOD);
return 0;
}
期望得分:\(100+100+100+100=400\)
實際得分:\(100+10+100+100=310\) 血虧QwQ