竞赛结果:Contestant,3202nd,Solved ABC,+22
todo
因为博主太困所以打算等下周空闲时间把这场和上场的题意补上。
A.Raise Both Hands 思路 打表(这能叫做一个表吗?)即可。
代码 1 2 3 4 5 6 7 8 9 10 11 void solve () { int l, r; cin >> l >> r; if (l && !r) cout << "Yes" << endl; else if (!l && r) cout << "No" << endl; else cout << "Invalid" << endl; }
B.Binary Alchemy 思路 按规则模拟即可,为了缩减代码量我直接采用了 max
和 min
来替代一层 if
判断,增加的复杂度无伤大雅。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 void solve () { int n; cin >> n; vector<vector<int >> vec (n + 1 , vector <int >(n + 1 )); for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= i; j++) cin >> vec[i][j]; int element = 1 ; for (int ele = 1 ; ele <= n; ele++) element = vec[max (element, ele)][min (ele, element)]; cout << element << endl; }
C.Word Ladder 思路 显然,因为修改本身没有限制,所以最少的修改次数必然是两个字符串不同字符的字符个数。
下面考虑如何让每次修改字典序最小。
显而易见,将一个字典序大的字符修改为一个字典序小的字符,字符串的整体字典序会下降,反之上升。而字典序的比较规则是从前往后比较,即为前面的字典序下降对整体字符串字典序的影响更大,而后面字符字典序上升对整体字符串字典序影响更小。
所以,为了让每次修改后的字典序是最优,我们首先正序修改会导致字典序下降的字符,让字典序“速降”,再倒序修改会导致字典序上升的字符,让字典序“缓升”。这样的解法就是最优的。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void solve () { string s, t; cin >> s >> t; vector<string> ans; vector<int > inc,dec; int n = s.size (); for (int i = 0 ; i < n; i++) { if (s[i] < t[i]) inc.push_back (i); else if (s[i] > t[i]) dec.push_back (i); } for (auto &v : dec) s[v] = t[v], ans.push_back (s); reverse (all (inc)); for (auto &v : inc) s[v] = t[v], ans.push_back (s); cout << ans.size () << endl; for (auto &s : ans) cout << s << endl; }
以下为补题部分。
D.Cross Explosion 思路 朴素的暴力 直接根据题意暴力即可,开启O3优化后,对于本题会TLE两个点,但是大部分样例都能过,证明只要在暴力上进行优化即可。
暴力的优化 暴力的优化一共有两个方向,一种是用数组维护当前节点的相邻节点,另外一种是 set
记录目前剩余节点并二分查找相邻节点。
我赛时写的第一种优化,没优化出来。
但这种优化好像有些边界条件模糊,还真不如二分set好懂,于是干脆只写二分set了。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 void solve () { int h, w, q; cin >> h >> w >> q; vector<set<int >> col (w + 1 ), row (h + 1 ); for (int i = 1 ; i <= h; i++) for (int j = 1 ; j <= w; j++) row[i].insert (j), col[j].insert (i); for (int i = 1 ; i <= h; i++) row[i].insert (0 ), row[i].insert (w + 1 ); for (int i = 1 ; i <= w; i++) col[i].insert (0 ), col[i].insert (h + 1 ); int ans = h * w; while (q--) { int x, y; cin >> x >> y; auto it = row[x].lower_bound (y), it2 = prev (it); if (*it == y) { ans--, row[x].erase (y), col[y].erase (x); continue ; } if (*it <= w) ans--, row[x].erase (*it), col[*it].erase (x); if (*it2) ans--, row[x].erase (*it2), col[*it2].erase (x); it = col[y].lower_bound (x), it2 = prev (it); if (*it <= h) ans--, row[*it].erase (y), col[y].erase (*it); if (*it2) ans--, row[*it2].erase (y), col[y].erase (*it2); } cout << ans << endl; }
E.Avoid K Partition 思路 时间复杂度 $O(N^2)$ 二维 dp
,定义 $dp[i]$ 为在选定点 $i$ 的时候,从 $1$ 选到 $i$ 的方案的总和。
初始值: $dp[1] = 1$ ,终值取 $dp[n+1]$ ,过程中的推导式为 $dp[n] = \sum_{1 \leq m \lt n} \left(0\text{ if }\sum_{k=m}^{n-1} A_i = K \text{ else } dp[m] \right) $。
注意:虽然这个式子中的$\sum A_i$部分可以用前缀和,但是时间复杂度最坏是$N^2$,这个推导式过不了本题目,要对其进行优化。
优化正解 可以发现转移只会从 $\sum^i_j {A_i \neq k}$ 的区间来,所以分别记录目前的总方案数和上述区间的方案数,则 $dp[i] = sum-invalid$。
开一个 map
进行缓存,令 $mp[pref]$ 等价前缀和为 $pref$ 时的合法方案数,则 $invalid = mp[sum-k]$。答案得以求出。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 constexpr int MOD = 998244353 ;using i64 = long long ;void solve () { int n; i64 k; cin >> n >> k; vector<int > vec (n + 1 ) ; for (int i = 1 ; i <= n; i++) cin >> vec[i]; vector<i64> dp (n + 1 ) ; dp[1 ] = 1 ; map<i64, int > mp; mp[0 ] = 1 ; i64 sum = 0 , all = 1 ; for (int i = 1 ; i <= n; i++) { sum += vec[i]; int invalid = mp[sum - k]; dp[i] = (all - invalid + MOD) % MOD; all = (all + dp[i]) % MOD; mp[sum] = (mp[sum] + dp[i]) % MOD; } cout << dp[n] << endl; }