杂题乱做 - 2023.10
- 写在前面
- CF1872G 贪心,结论,暴力 2000 2.trick 5.人类智慧
- The 2021 ICPC Asia Jinan Regional Contest - J Determinant 大概 2200 2.trick 5.人类智慧
- CF73D 图论,贪心,并查集 2200 2.trick
- CF1822E 贪心,构造 1600 2.trick
- CF1870D 反悔贪心 1800 2.trick
- CF1851G 贪心,单调性,离线 2000 2.trick 4.须魔改某一算法的题
- CF1634F 对差分的本质理解 2700 2.trick 4.须魔改某一算法的题 5.人类智慧
- CF1866D DP,牛逼的状态设计 2300 2.trick 4.须魔改某一算法的题 5.人类智慧
- CF1100E 二分答案,有向无环图的性质 2200 2.trick 5.人类智慧
- CF1861D 贪心,枚举 1800 2.trick
- 写在最后
写在前面
如题,杂题乱做。
有的时候闲得无聊就写上几题。
唉,菜。
加训!
CF1872G 贪心,结论,暴力 2000 2.trick 5.人类智慧
https://codeforces.com/contest/1872/problem/G
记非 1 位置坐标依次为:,显然答案只能是非 1 的位置,另外显然当非 1 位置较多时,答案为 肯定是最优的。
于是考虑定一个非 1 元素乘积的上界,当乘积大于该上界时就直接输出 ,否则 较小,直接暴力枚举答案区间即可。
上界定为 就差不多了,此时 ,总复杂度 。
为了方便写了 py,其实用 int128 也行。
复制复制T = int(input()) lim = 2 ** 60 while T > 0: T = T - 1 n = int(input()) a = [0] p = [0] sum1 = [0] sum = [1] prod = 1 s = input().split(' ') for i in range(1, n + 1): a.append(int(s[i - 1])) sum1.append(sum1[i - 1] + a[i]) if a[i] > 1: p.append(i) if prod < lim: prod = prod * a[i] if prod >= lim: print(p[1], p[-1]) continue for i in range(1, len(p)): sum.append(a[p[i]]) sum[i] = sum[i] * sum[i - 1] delta = 0 ansl, ansr = (1, 1) for i in range(1, len(p)): for j in range(i + 1, len(p)): l, r = p[i], p[j] if ((sum[j] / sum[i - 1] - sum1[r] + sum1[l - 1]) > delta): ansl, ansr = (l, r) delta = sum[j] / sum[i - 1] - sum1[r] + sum1[l - 1] print(ansl, ansr) ''' 1 4 2 1 1 3 '''
学到了典中典之小范围暴力大范围特判。
The 2021 ICPC Asia Jinan Regional Contest - J Determinant 大概 2200 2.trick 5.人类智慧
https://pintia.cn/problem-sets/1459829212832296960
可以高消,但是太大了,不能直接算。
一个结论:已知 ,那么在模奇数意义下, 和 奇偶性不同。
于是考虑在模某个大质数意义下求 ,再判断在模意义下是否与 是否相同即可,若相同则为 +
,否则为 -
。
牛逼。
CF73D 图论,贪心,并查集 2200 2.trick
https://codeforces.com/contest/73/problem/D
读不懂题建议参考此题洛谷翻译:Link。
设原图中有 个连通块,则至少要连 条 tunnel。
点数为 的连通块 可以连 条边,又因为是无向边,则原图有解等价于:
再考虑加边使得原图有解,显然仅有连通两个连通块的加边才是有贡献的。考虑加入了一条将点数为 , 的两个连通块连接的边,则加边后仅需判断:
即:
发现当 时,等式右侧无增减,仅有左侧 + 2,此时对判断条件贡献最小;手玩下发现当 为最小值时,连边的贡献最大。
则每次都将 最小的两个连通块拿出来合并,再检查是否满足有解条件即可。
使用并查集+小根堆维护即可。总时间复杂度上限 级别。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 1e6 + 10; //============================================================= int n, m, k; int num, sum, ans, fa[kN], sz[kN]; bool vis[kN]; std::priority_queue <int> q; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } int find(int x_) { return fa[x_] == x_ ? x_ : fa[x_] = find(fa[x_]); } void merge(int x_, int y_) { int fax = find(x_), fay = find(y_); if (fax == fay) return ; fa[fax] = fay; sz[fay] += sz[fax]; } //============================================================= int main() { //freopen("1.txt", "r", stdin); n = read(), m = read(), k = read(); for (int i = 1; i <= n; ++ i) fa[i] = i, sz[i] = 1; for (int i = 1; i <= m; ++ i) { int u_ = read(), v_ = read(); merge(u_, v_); } for (int i = 1; i <= n; ++ i) { int fai = find(i); if (vis[fai]) continue; vis[fai] = true; ++ num; sum += std::min(sz[fai], k); q.push(-sz[fai]); } while (sum < 2 * (num - 1)) { int szu = -q.top(); sum -= std::min(szu, k), q.pop(); int szv = -q.top(); sum -= std::min(szv, k), q.pop(); ++ ans; -- num; sum += std::min(szu + szv, k); q.push(-szu - szv); } printf("%d\n", ans); return 0; }
CF1822E 贪心,构造 1600 2.trick
https://codeforces.com/contest/1822/problem/E
翻 unsolved 里找到的看了十几分钟发现现在还不会,太废物了。
首先当 为奇数,或者有一种字符出现大于 次时无解。
设 为相等的对称位置的对数, 为字母 所有相等的对称位置的对数。
手玩下发现最好的修改方案是把两对字母不同的相等的对称位置交换,这样可以一次消掉两组,则最理想情况下仅进行这样的操作即可;另外对于每种字母的所有相等的对称位置都需要进行至少一次操作,则修改次数的下限为 。
发现总能通过构造达到这个下限,则答案即为。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 2e5 + 10; //============================================================= int n; char s[kN]; int num, cnt[26], pr[26]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } void Solve() { if (n % 2 == 1) { printf("-1\n"); return ; } num = 0; for (int i = 0; i < 26; ++ i) cnt[i] = pr[i] = 0; for (int i = 1; i <= n; ++ i) ++ cnt[s[i] - 'a']; for (int i = 0; i < 26; ++ i) { if (cnt[i] > n / 2) { printf("-1\n"); return ; } } for (int i = 1; i <= n / 2; ++ i) { if (s[i] == s[n - i + 1]) { ++ num; ++ pr[s[i] - 'a']; } } int ans = (num + 1) / 2; for (int i = 0; i < 26; ++ i) ans = std::max(ans, pr[i]); printf("%d\n", ans); } //============================================================= int main() { //freopen("1.txt", "r", stdin); int T = read(); while (T --) { n = read(); scanf("%s", s + 1); Solve(); } return 0; }
CF1870D 反悔贪心 1800 2.trick
https://codeforces.com/contest/1870/problem/D
反悔贪心。
操作代价 越小越好,其次操作位置 越靠后越好,于是先排序。考虑先把钱全用在最优的操作上,对于剩下的钱考虑在枚举操作的过程中进行反悔,如果存在操作使得可以撤回之前的一次操作并加钱使得修改位置更靠后则反悔一次。
记录最后进行操作的次数和代价即可。注意反悔次数不能超过最后进行的操作的次数。
上述过程中维护差分数组即可得到操作后的序列。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 2e5 + 10; const int kInf = 1e9 + 2077; //============================================================= int n, k, d[kN]; struct Opt { int pos, val; } c[kN]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } bool cmp(Opt fir_, Opt sec_) { if (fir_.val != sec_.val) return fir_.val < sec_.val; return fir_.pos > sec_.pos; } void Init() { n = read(); for (int i = 1; i <= n; ++ i) d[i] = 0; for (int i = 1; i <= n; ++ i) c[i] = (Opt) {i, read()}; k = read(); std::sort(c + 1, c + n + 1, cmp); } void Solve() { int pos = 0, minus = 0, num = kInf; for (int i = 1; i <= n; ++ i) { if ((c[i].val - minus) > k || c[i].pos < pos || !num) continue; int r = std::min(k / (c[i].val - minus), num); d[pos + 1] += r, d[c[i].pos + 1] -= r; num = r; k -= r * (c[i].val - minus); pos = c[i].pos, minus = c[i].val; } for (int i = 1; i <= n; ++ i) d[i] += d[i - 1], printf("%d ", d[i]); printf("\n"); } //============================================================= int main() { //freopen("1.txt", "r", stdin); int T = read(); while (T --) { Init(); Solve(); } return 0; } /* 2 3 4 7 */
CF1851G 贪心,单调性,离线 2000 2.trick 4.须魔改某一算法的题
https://codeforces.com/problemset/problem/1851/G
典中典之离线加边。
从 出发有 的能量等价于只能经过 的点,发现有单调性,于是套路地离线询问并按照 排序,在此过程中每次将 的点加入图中,并查集维护连通性即可。
时间复杂度 级别。
强制在线可以 Kruscal 重构树。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 2e5 + 10; //============================================================= int n, m, q; int h[kN], fa[kN]; bool yes[kN], ans[kN]; std::vector <int> v[kN]; struct Node { int h, id; } a[kN]; struct Query { int a, b, h, id; } query[kN]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } int find(int x_) { return fa[x_] == x_ ? x_ : fa[x_] = find(fa[x_]); } void merge(int x_, int y_) { int fax = find(x_), fay = find(y_); if (fax == fay) return ; fa[fax] = fay; } bool cmp_node(Node fir_, Node sec_) { return fir_.h < sec_.h; } bool cmp_query(Query fir_, Query sec_) { return fir_.h < sec_.h; } void Init() { n = read(), m = read(); for (int i = 1; i <= n; ++ i) { fa[i] = i; yes[i] = ans[i] = 0; v[i].clear(); } for (int i = 1; i <= n; ++ i) { h[i] = read(); a[i] = (Node) {h[i], i}; } for (int i = 1; i <= m; ++ i) { int u_ = read(), v_ = read(); v[u_].push_back(v_); v[v_].push_back(u_); } q = read(); for (int i = 1; i <= q; ++ i) { int a_ = read(), b_ = read(), e_ = read(); query[i] = (Query) {a_, b_, h[a_] + e_, i}; } } void Solve() { std::sort(a + 1, a + n + 1, cmp_node); std::sort(query + 1, query + q + 1, cmp_query); for (int i = 1, j = 1; i <= q; ++ i) { while (j <= n && a[j].h <= query[i].h) { yes[a[j].id] = 1; for (auto v_: v[a[j].id]) { if (yes[v_]) merge(a[j].id, v_); } ++ j; } ans[query[i].id] = (find(query[i].a) == find(query[i].b)); } } //============================================================= int main() { //freopen("1.txt", "r", stdin); int T = read(); while (T --) { Init(); Solve(); for (int i = 1; i <= q; ++ i) printf("%s\n", ans[i] ? "YES" : "NO"); printf("\n"); } return 0; }
CF1634F 对差分的本质理解 2700 2.trick 4.须魔改某一算法的题 5.人类智慧
https://codeforces.com/contest/1634/problem/F
好玩题。
先令 ,则仅需检查 是否全为 0。
发现这题带 不好跑,又是区间修改,只得考虑差分。但是又要动态检查,这如何是好?
考虑差分的本质。我们可以将区间加 转化为差分数组上的 加 减,是因为修改的增量 满足 ,于是可以构建差分数组:,,则通过维护差分数组即可在修改完成后通过 来递推出 。
同理,区间加斐波那契数列也存在这样的递推关系:,则考虑构建差分数组:,,,则区间加 可转化为三次单点修改:,,, 则同理有:。
发现 全为 0 当且仅当差分数组 全为 0,于是在单点修改 的过程中维护 有多少个位置为 0 即可。
总时间复杂度 级别,注意随时取模。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 3e5 + 10; //============================================================= int n, q, num; LL p, f[kN], c[kN], d[kN]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } void Init() { n = read(), q = read(), p = read(); f[1] = f[2] = 1; for (int i = 3; i <= n; ++ i) f[i] = (f[i - 1] + f[i - 2]) % p; for (int i = 1; i <= n; ++ i) c[i] = read() % p; for (int i = 1; i <= n; ++ i) c[i] = (c[i] - read() % p + p) % p; d[1] = c[1] % p, d[2] = (c[2] - c[1] + p) % p; for (int i = 3; i <= n; ++ i) { d[i] = (c[i] - c[i - 1] - c[i - 2] + 2 * p) % p; } for (int i = 1; i <= n; ++ i) num += (!d[i]); } void modify(int pos_, int val_) { if (pos_ > n) return ; num -= (!d[pos_]); d[pos_] = (d[pos_] + val_ + p) % p; num += (!d[pos_]); } //============================================================= int main() { // freopen("1.txt", "r", stdin); Init(); while (q --) { char s[5]; scanf("%s", s + 1); int l = read(), r = read(); int type = ((s[1] == 'A') ? 1 : -1), ntype = ((s[1] == 'B') ? 1 : -1); modify(l, type); modify(r + 1, ntype * f[r - l + 1 + 1]); modify(r + 2, ntype * f[r - l + 1]); printf("%s\n", (num == n) ? "YES" : "NO"); } return 0; }
学到了灵活差分。
差分的本质是递推。
CF1866D DP,牛逼的状态设计 2300 2.trick 4.须魔改某一算法的题 5.人类智慧
https://codeforces.com/problemset/problem/1866/D
不太明白这个 DP 具体是怎么想到的、、、就当是学到了。
考虑对于某一列数,如果最终的方案中在第 列中取了 个数,则有贡献的一定是最大的 个。于是先把每列数取出来并降序排序。
发现 很小,考虑按列 DP。设 表示取数方案中最靠后的一列为 ,且在第 列中取了 个数时的最大价值和,注意 也有意义。转移时先枚举当前可以取数的区间的右端点 ,然后考虑最后取了 中的哪一列 ,再倒序枚举 表示在第 列中取了多少。在更新 的同时维护 表示通过取前 列可以得到的最大价值和来更新 和 。
不太好解释转移,详见代码。
复杂度 级别。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 11; const int kM = 1e5 + 10; //============================================================= int n, m, k, a[kN][kM]; std::vector <int> b[kM]; LL ans, f[kM][kN]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } //============================================================= int main() { //freopen("1.txt", "r", stdin); n = read(), m = read(), k = read(); for (int i = 1; i <= n; ++ i) { for (int j = 1; j <= m; ++ j) { a[i][j] = read(); b[j].push_back(-a[i][j]); } } for (int i = 1; i <= m; ++ i) std::sort(b[i].begin(), b[i].end()); for (int i = k; i <= m; ++ i) { LL g = 0; for (int j = 0; j <= n; ++ j) g = std::max(g, f[i - k][j]); for (int j = i - k + 1; j <= i; ++ j) { LL pre = g; for (int l = n; l; -- l) { if (l + 1 <= n) f[j][l + 1] = std::max(f[j][l + 1], f[j][l] - b[j][l]); g = std::max(g, f[j][l]); } f[j][0] = std::max(f[j][0], pre); f[j][1] = std::max(f[j][1], pre - b[j][0]); } } for (int i = 1; i <= m; ++ i) { for (int j = 0; j <= n; ++ j) { ans = std::max(ans, f[i][j]); } } printf("%lld\n", ans); return 0; }
CF1100E 二分答案,有向无环图的性质 2200 2.trick 5.人类智慧
https://codeforces.com/contest/1100/problem/E
应该是典中典之 DAG 性质,忘了什么时候见过了。
对于一个 DAG,在其中加入若干条由拓扑序较小的点指向拓扑序较大的点的边,新图仍为 DAG。
先套一个二分答案,问题变为检测能否通过仅翻转 的边使得原图变为 DAG。于是先考虑 的边构成的子图,如果其中有环则寄,否则可以得到原图的一个拓扑序,根据上述结论再判断 的每条边是否需要翻转即可。
总时间复杂度 级别。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 1e5 + 10; const int kM = kN; //============================================================= int n, m, ans1; std::vector <int> ans; int topnum, into[kN], top[kN]; int edgenum, head[kN], v[kM], w[kM], ne[kM]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } void Add(int u_, int v_, int w_) { v[++ edgenum] = v_; w[edgenum] = w_; ne[edgenum] = head[u_]; head[u_] = edgenum; } void Init() { topnum = 0; for (int i = 1; i <= n; ++ i) into[i] = top[i] = 0; } bool Check(int lim_) { Init(); for (int u_ = 1; u_ <= n; ++ u_) { for (int i = head[u_]; i; i = ne[i]) { if (w[i] <= lim_) continue; into[v[i]] ++; } } std::queue <int> q; for (int i = 1; i <= n; ++ i) { if (!into[i]) q.push(i); } while (!q.empty()) { int u_ = q.front(); q.pop(); top[u_] = ++ topnum; for (int i = head[u_]; i; i = ne[i]) { if (w[i] <= lim_) continue; if (!(--into[v[i]])) q.push(v[i]); } } if (topnum != n) return false; ans.clear(); for (int u_ = 1; u_ <= n; ++ u_) { for (int i = head[u_]; i; i = ne[i]) { if (w[i] > lim_) continue; if (top[u_] > top[v[i]]) ans.push_back(i); } } return true; } //============================================================= int main() { //freopen("1.txt", "r", stdin); n = read(), m = read(); for (int i = 1; i <= m; ++ i) { int u_ = read(), v_ = read(), w_ = read(); Add(u_, v_, w_); } for (int l = 0, r = 1e9; l <= r; ) { int mid = (l + r) >> 1; if (Check(mid)) { ans1 = mid; r = mid - 1; } else { l = mid + 1; } } printf("%d %d\n", ans1, ans.size()); for (auto x: ans) printf("%d ", x); return 0; }
CF1861D 贪心,枚举 1800 2.trick
https://codeforces.com/problemset/problem/1861/D
很重要的性质是原数列均为正。
如果只能乘正数时那就是傻逼,考虑原数列中的所有极长递增子序列,从右向左依次对它们操作即可。如果可以乘负数,则可以把一段递减子序列变成一段负的递增子序列。
显然最后的数列形态中仅有一段前缀为负,于是考虑枚举最终形态里正负的分界线,负的部分的所需操作即其中极长递减子序列的数量,正的部分所需操作即其中极长递增子序列的数量,求和即为所需操作数。
两种数量可以考虑每次分界线移动时被影响的相邻两个元素的大小关系递推实现。总时间复杂度 级别。
注意特判负的部分长度为 1 的时候。代码中枚举的是正的部分的左端点。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 2e5 + 10; const int kInf = 1e9 + 2077; //============================================================= int n, a[kN]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } void Solve() { int cnt1 = 0, cnt2 = 0, ans = kInf; for (int i = 1; i <= n; ++ i) cnt1 += (a[i - 1] >= a[i]); for (int i = 1; i <= n + 1; ++ i) { if (i == 2) cnt2 = 1; ans = std::min(ans, cnt1 + cnt2); if (i > 1) cnt2 += (a[i - 1] <= a[i]); if (i + 1 <= n) cnt1 -= (a[i] >= a[i + 1]); } printf("%d\n", ans); } //============================================================= int main() { //freopen("1.txt", "r", stdin); int T = read(); while (T --) { n = read(); for (int i = 1; i <= n; ++ i) a[i] = read(); Solve(); } return 0; }
写在最后
我是什么几把??
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】