Codeforces Round #837 (Div. 2)
A. Hossam and Combinatorics (CF 1771 A)
题目大意
给定一个长度为的数组,问有多少个数对其差的绝对值等于该数组的极差。
解题思路
若最大值和最小值相等,则答案为
否则就为最大值个数和最小值个数的乘积的两倍(顺序有关的数对)
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; #define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i) #define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i) int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int t; cin >> t; while(t--){ int n; cin >> n; map<int, int> cnt; FOR(i, 0, n){ int x; cin >> x; cnt[x] ++; } if (cnt.size() == 1){ cout << 1ll * cnt.begin()->second * (cnt.begin()->second - 1) << '\n'; }else cout << 1ll * cnt.begin()->second * cnt.rbegin()->second * 2 << '\n'; } return 0; }
B. Hossam and Friends (CF 1771 B)
题目大意
给定一个数组,其值为。以及给定 条规则,第 条规则规定不能同时在一个数组。
问有多少个连续的子数组不违反这 条规则。
解题思路
固定一个右端点,考虑合法的子数组的左端点,是一个从右端点往左的一个连续的区间。
随着子数组的右端点不断向右移动,会发现其合法的子数组的最小左端点是不断往右移动的。其左端点可继承上一状态。
因此可采用双指针法维护。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; #define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i) #define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i) int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int t; cin >> t; while(t--){ int n, m; cin >> n >> m; int l = 1; vector<vector<int>> edge(n + 1); FOR(i, 0, m){ int u, v; cin >> u >> v; if (u > v) swap(u, v); edge[v].push_back(u); } LL ans = 0; for(int i = 1; i <= n; ++ i){ for(auto v : edge[i]){ if (l <= v) l = v + 1; } ans += 0ll + max(0, i - l + 1); } cout << ans << '\n'; } return 0; }
C. Hossam and Trainees (CF 1771 C)
题目大意
给定个数字 ,若存在一对 不互质,则输出 ,否则输出 。
解题思路
对这个数质因数分解,记录其中出现过的质数即可。
若某个质数再次出现则为,否则为 。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; #define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i) #define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i) const LL p_max = 1E6 + 100; LL pr[p_max], p_sz; void get_prime() { static bool vis[p_max]; FOR (i, 2, p_max) { if (!vis[i]) pr[p_sz++] = i; FOR (j, 0, p_sz) { if (pr[j] * i >= p_max) break; vis[pr[j] * i] = 1; if (i % pr[j] == 0) break; } } } set<int> cnt; bool get_factor(LL x) { LL t = sqrt(x + 0.5); for (LL i = 0; pr[i] <= t; ++i) if (x % pr[i] == 0) { if (cnt.find(pr[i]) != cnt.end()) return true; cnt.insert(pr[i]); while (x % pr[i] == 0) { x /= pr[i]; } } if (x > 1) { if (cnt.find(x) != cnt.end()) return true; cnt.insert(x); } return false; } int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); get_prime(); int t; cin >> t; while(t--){ int n; cin >> n; bool ok = false; cnt.clear(); FOR(i, 0, n){ int x; cin >> x; ok |= get_factor(x); } if (ok) cout << "YES\n"; else cout << "NO\n"; } return 0; }
D. Hossam and (sub-)palindromic tree (CF 1771 D)
题目大意
给定一棵树,节点有字母。点到点 的路径表示成其路径点的字母组成的字符串。
定义一个字符串的价值为该字符串长度最长的回文子序列(可不连续的)的长度。
问所有路径对应的字符串的价值的最大值。
解题思路
先考虑给定一个串如何求出最长回文子序列。
设表示子字符串 的最长回文子序列。
- 如果,则
- 否则
其时间复杂度为
再回到树上,由于字符串变成了路径,如果设 表示一段路径,一端在点,另一段在点 ,会发现转移是一样的(因为路径是唯一的,并且也是一维的),无非就是变成了其点的父亲或者某个儿子而已。
所以同样直接做即可。找儿子的话用了以及倍增的方式(深度差减一),故时间复杂度为
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; #define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i) #define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i) const int N = 2e3 + 8; const int SP = 15; vector<vector<int>> G; int pa[N][SP]; int dep[N]; int dp[N][N]; int ans, n; string s; void dfs(int u, int fa) { pa[u][0] = fa; dep[u] = dep[fa] + 1; FOR (i, 1, SP) pa[u][i] = pa[pa[u][i - 1]][i - 1]; for (int& v: G[u]) { if (v == fa) continue; dfs(v, u); } } int lca(int u, int v) { if (dep[u] < dep[v]) swap(u, v); int t = dep[u] - dep[v]; FOR (i, 0, SP) if (t & (1 << i)) u = pa[u][i]; FORD (i, SP - 1, -1) { int uu = pa[u][i], vv = pa[v][i]; if (uu != vv) { u = uu; v = vv; } } return u == v ? u : pa[u][0]; } int get_next(int u, int t){ FOR (i, 0, SP) if (t & (1 << i)) u = pa[u][i]; return u; } int solve(int u, int v){ if (dp[u][v] != 0) return dp[u][v]; if (u == v) return dp[u][v] = 1; int fa = lca(u, v); int nxtu = pa[u][0], nxtv = pa[v][0]; if (u == fa){ nxtu = get_next(v, dep[v] - dep[fa] - 1); }else if (v == fa){ nxtv = get_next(u, dep[u] - dep[fa] - 1); } dp[u][v] = max(solve(nxtu, v), solve(u, nxtv)); if (s[u] == s[v]){ if (nxtu == v && nxtv == u) dp[u][v] = max(dp[u][v], 2); else dp[u][v] = max(dp[u][v], solve(nxtu, nxtv) + 2); } return dp[u][v]; } int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int t; cin >> t; while(t--){ cin >> n >> s; G.clear(); G.resize(n + 1); FOR(i, 1, n){ int u, v; cin >> u >> v; -- u; -- v; G[u].push_back(v); G[v].push_back(u); } dfs(0, 0); ans = 0; for(int i = 0; i < n; ++ i) for(int j = 0; j < n; ++ j){ ans = max(solve(i, j), ans); } cout << ans << '\n'; memset(dp, 0, sizeof(dp)); } return 0; }
E. Hossam and a Letter (CF 1771 E)
题目大意
给定一个的格子,有完美格子,一般格子,坏格子。
先要选择一些格子,形成一个字母 ,要求
- 格子排成两条垂直线和一条水平线
- 垂直线两端对其
- 水平线紧挨垂直线
- 水平线不得再垂直线的两端
- 不得选择坏格子,至多选择一个一般格子
问选的格子数的最大值。
解题思路
预处理每个格子往上往下延伸,分别不选择一般格子和只选择一个一般格子的最长延伸距离,然后枚举水平线的位置(一个行坐标,两个列坐标),对谁用了一般格子分类讨论下,取最大值即可。
预处理部分建议阅读jiangly代码,写得非常简洁美妙。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; #define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i) #define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i) const int N = 4e2 + 8; int n, m; string s[N]; int up[2][N][N], down[2][N][N]; int ans; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n >> m; for(int i = 0; i < n; ++ i) cin >> s[i]; for(int i = 0; i < n; ++ i) for(int j = 0; j < m; ++ j){ if (s[i][j] == '#') continue; int med = 0; for(int k = i; k >= 0; -- k){ if (s[k][j] == 'm'){ up[med][i][j] = i - k; med ++; }else if (s[k][j] == '#'){ up[med][i][j] = i - k; ++ med; if (med == 1){ up[med][i][j] = i - k; ++ med; } break; } if (med > 1) break; } if (med == 0){ up[med][i][j] = i + 1; ++ med; } if (med == 1){ up[med][i][j] = i + 1; ++ med; } assert(med == 2); med = 0; for(int k = i + 1; k < n; ++ k){ if (s[k][j] == 'm'){ down[med][i][j] = k - i - 1; med ++; }else if (s[k][j] == '#'){ down[med][i][j] = k - i - 1; ++ med; if (med == 1){ down[med][i][j] = k - i - 1; ++ med; } break; } if (med > 1) break; } if (med == 0){ down[med][i][j] = n - i - 1; ++ med; } if (med == 1){ down[med][i][j] = n - i - 1; ++ med; } assert(med == 2); } ans = 0; for(int i = 1; i < n - 1; ++ i) for(int j = 1; j < m - 1; ++ j){ int med = 0; for(int k = j; k < m - 1; ++ k){ if (s[i][k] == '#') break; med += (s[i][k] == 'm'); if (med > 1) break; auto check = [&](int up0, int up1, int down0, int down1){ int upp = min(up[up0][i][j - 1], up[up1][i][k + 1]); int downn = min(down[down0][i][j - 1], down[down1][i][k + 1]); if (upp > 1 && downn >= 1){ ans = max(ans, 2 * (upp + downn) + k - j + 1); } }; if (med){ check(0,0,0,0); }else{ check(1, 0, 0, 0); check(0, 1, 0, 0); check(0, 0, 1, 0); check(0, 0, 0, 1); } } } cout << ans << '\n'; return 0; }
F. Hossam and Range Minimum Query (CF 1771 F)
题目大意
给定个数字的数组,以及组询问。每组询问包含两个数 ,问 中出现次数是奇数的最小数是什么。
强制在线。
解题思路
多亏此题,我忽然对主席树的原理和写法彻底悟了(有的人以前只会口糊,然后交给队友写)
如果离线的话可以用莫队。
在线的话,由于需要维护任意区间内的信息,一般采用主席树,而主席树保存的是区间的信息,要得到区间 的信息的话,需要区间 的信息与区间 的信息作差。
我们先对原数组离散化,然后建立一棵值域主席树。假设离散化为的个数为个,考虑维护什么信息。
如果维护每个数的出现次数,那么作差的复杂度是,那复杂度至少是,不可行。我们得让作差的复杂度为 或者
由于是出现奇数次,可以维护区间异或和,该异或和不为意味着该区间一定有出现奇数次的数,但为的话则不一定,会被精心构造的数据卡掉。
但由于我们想看的是数是否一样,而数的大小这一性质可以丢掉,这意味着我们可以对原数组进行一个随机映射,只要保证相同的数映射到相同的值即可。通常的做法就是将这些数随机映射到 中,这样能够保证出现上述所说的情况的概率非常小。
拿映射后的值进行异或,然后在主席树上对答案进行二分,找到最小的异或值不为0的下标即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; #define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i) #define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i) const int M = 2e5 + 8; using ull = unsigned long long; int n, m, q; namespace tree { #define mid ((l + r) >> 1) #define lson l, mid #define rson mid + 1, r const int MAGIC = M * 30; struct P { ull sum; int ls, rs; } tr[MAGIC] = {{0, 0, 0}}; int sz = 1; int N(ull sum, int ls, int rs) { if (sz == MAGIC) assert(0); tr[sz] = {sum, ls, rs}; return sz++; } int ins(int o, int x, ull v, int l = 1, int r = m) { if (x < l || x > r) return o; const P& t = tr[o]; if (l == r) return N(t.sum ^ v, 0, 0); return N((t.sum ^ v), ins(t.ls, x, v, lson), ins(t.rs, x, v, rson)); } int query(int o1, int o2, int l = 1, int r = m) { if (tr[o1].sum == tr[o2].sum) return -1; if (l == r) return l; int ls1 = tr[o1].ls; int ls2 = tr[o2].ls; if (tr[ls1].sum != tr[ls2].sum) return query(ls1, ls2, lson); else return query(tr[o1].rs, tr[o2].rs, rson); } } int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n; vector<int> a(n); for(auto &i : a) cin >> i; vector<int> rank = a; sort(rank.begin(), rank.end()); rank.erase(unique(rank.begin(), rank.end()), rank.end()); m = rank.size(); vector<ull> h(m); vector<int> tree(n + 1); std::mt19937_64 rng(std::chrono::steady_clock::now().time_since_epoch().count()); for(int i = 0; i < m; ++ i){ h[i] = rng(); } for(int i = 0; i < n; ++ i){ int pos = lower_bound(rank.begin(), rank.end(), a[i]) - rank.begin(); tree[i + 1] = tree::ins(tree[i], pos + 1, h[pos]); } cin >> q; int ans = 0; while(q--){ int l, r; cin >> l >> r; l ^= ans; r ^= ans; -- l; ans = tree::query(tree[l], tree[r]); if (ans == -1) ans = 0; else ans = rank[ans - 1]; cout << ans << '\n'; } return 0; }
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/16988126.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步