第一題:格雷碼,構造,逆向思維
第二題:括號樹,括號序列,棧,dfs,構造
第三題:樹上的數,貪心,鏈表,構造
D1T1:格雷碼
思路:第一眼看到格雷碼感覺格外親切,年紀大了,熬夜熬不動了,記憶力也衰退了不少,隱約記得格雷碼曾經在某一次初賽的試題裏出現過,當然也可能是模擬賽。包括SCOI 2015年還有一題《超級格雷碼》,騰訊2016年筆試有一題是生成格雷碼,LeetCode (一個面試海外IT公司很有用的題庫)也有格雷碼的題目,格雷碼和二進制之間的轉換應該是一個經典問題。
迴歸到本題當中,本題充分考察了選手逆向思維,如果80%的數據,採用構造格雷碼的方法,那麼根據k反推格雷碼的規律也應該能夠想出來。如果K的當前位爲1,則格雷碼該位也爲1,然後把K剩下未處理的位取反,如果K的當前位爲0,則對應的格雷碼也爲0,但不取反, 可以結合代碼理解,建議動動筆推導一下。
#include<iostream> #include<cstdio> using namespace std; int main() { freopen("code.in","r",stdin); freopen("code.out","w",stdout); unsigned long long n, k; cin >> n >> k; for(long long i = n-1; i >= 0; --i) { if ((k >> i)&1) { cout << "1"; k = ~k; } else { cout << "0"; } } fclose(stdin); fclose(stdout); return 0; }
D1T2: 括號樹
思路:括號序列和樹上問題結合,點贊。其次特殊性質fi = i - 1 是一條鏈了,都是很好的得部分分的性質,鏈上合法括號序列很簡單,所以這題沒有50分以上的選手都值得反思。
括號序列如果你能想到stack然後計算對答案的貢獻,其實還是可做的,首先建樹從樹根往下跑dfs,用棧來記錄途中的括號,再考慮到一個位置對答案有貢獻,能貢獻的肯定是右括號,過程中記錄待匹配的左括號即可,注意疊加即可,如果你想清楚鏈的問題,相信樹的問題也能想通。所以本題仍舊是一個樹的經典問題,如何在dfs過程中維護我們想要的數據。
注意結果開long long,在oitiku上試了一下不開longlong,只有50分。。。如果真的是longlong卡50分,還不如在題目裏提示一下大家結果超int,RP++
#include <iostream> #include <cstdio> #include <stack> using namespace std; const int maxn = 500000 + 10; struct Edge { int v, next; } e[2 * maxn]; int n, fa[maxn], f[maxn], cnt[maxn], tot = 0; string s; stack<int> stk; long long ans = 0, current = 0; void add(int u, int v) { tot += 1; e[tot].v = v; e[tot].next = f[u]; f[u] = tot; } void dfs(int x) { int cur = 0; if (!stk.empty() && s[stk.top() - 1] == '(' && s[x - 1] == ')') { cur = stk.top(); stk.pop(); cnt[x] = cnt[fa[cur]] + 1; } else { cnt[x] = 0; stk.push(x); } current += cnt[x]; ans = ans ^ (x * current); for (int i = f[x]; i > 0; i = e[i].next) { int v = e[i].v; dfs(v); } current -= cnt[x]; if (cur) stk.push(cur); else stk.pop(); } int main() { freopen("brackets.in","r",stdin); freopen("brackets.out","w",stdout); cin >> n; cin >> s; for (int i = 2; i <= n; ++i) { scanf("%d", &fa[i]); add(fa[i], i); } dfs(1); cout << ans << endl; fclose(stdin); fclose(stdout); return 0; }
D1T3:樹上的數
題目大意是帶權樹,n-1條邊,刪邊會調換連接這條邊的兩個點的權值,最後按點權排序,求結點編號的字典序最小的解。本題難度是有點大,但部分分給的也不少。
思路:考試時長3.5小時的情況下,第三題有鏈,有菊花圖(n-1度的點是大菊花)拿個60分,豈不是DAY1 260還是美滋滋的。不少能力出色的選手由於無法駕馭自己的能力,導致最後還不如專心寫部分分,這樣的例子每年都數不勝數。鏈和菊花圖本題給分數不低,且作爲突破口思考是非常值得的。對於普通選數從構思到寫和調試,一共3.5小時還是有點緊。
根據字典序最小,前面儘可能的小,大概能摸到一些貪心的思路。
本題出人已經在知乎上給出做法了,寫的很詳細:
https://www.zhihu.com/question/351347604/answer/896344216
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; struct Edge { int u, v, next; } e[2005 * 2]; int n, p[2005]; int pre[2005][2005], nxt[2005][2005]; int cnt[2005], f[2005], ans[2005], tot = 0; void add(int u, int v) { tot += 1; e[tot].u = u; e[tot].v = v; e[tot].next = f[u]; f[u] = tot; } bool check(int l, int m, int r) { if (pre[m][r] == -1) return false; if (nxt[m][l] == -1) return false; if (nxt[m][l] == r) return false; if (nxt[m][l] == 0 && pre[m][r] == n + 1 && cnt[m] != 2) return false; return true; } void un(int l, int m, int r) { nxt[m][pre[m][r]] = nxt[m][l]; pre[m][nxt[m][l]] = pre[m][r]; pre[m][r] = nxt[m][l] = -1; cnt[m]--; } bool remove(int x, int y, int fa) { if (x == y) { un(fa, x, n + 1); return true; } for (int i = f[x]; i; i = e[i].next) { int v = e[i].v; if (v == fa) continue; if (remove(v, y, x)) { un(fa, x, v); return true; } } return false; } int find(int x, int fa) { int result = n; if (check(fa, x, n + 1) && x < n) result = x; for (int i = f[x]; i; i = e[i].next) { int v = e[i].v; if (v == fa) continue; if (check(fa, x, v)) result = min(result, find(v, x)); } return result; } void work() { cin >> n; tot = 0; for (int i = 0; i <= n; ++i) f[i] = 0; for (int i = 1; i <= n; ++i) scanf("%d", &p[i]); for (int i = 1; i <= n; ++i) { for (int j = 0; j <= n + 2; ++j) pre[i][j] = nxt[i][j] = j; } for (int i = 1; i <= n; ++i) cnt[i] = 2; for (int i = 1; i < n; ++i) { int u, v; scanf("%d%d", &u, &v); add(u, v); add(v, u); cnt[u] += 1; cnt[v] += 1; } for (int i = 1; i <= n; ++i) { int np = find(p[i], 0); ans[i] = np; remove(p[i], np, 0); } for (int i = 1; i < n; ++i) printf("%d ", ans[i]); printf("%d\n", ans[n]); } int main() { freopen("tree.in", "r", stdin); freopen("tree.out", "w", stdout); int T; cin >> T; while (T--) { work(); } return 0; fclose(stdin); fclose(stdout); }