Educational Codeforces Round 161 (Rated for Div. 2)
写在前面
比赛地址:https://codeforces.com/contest/1922
D 没调出来亏炸了,第一次体验赛后五分钟过题的快感。
痛苦的大二上终于结束了,本学期一半的痛苦都来自于傻逼大物实验。
下学期课少了好多,而且早八和晚八都少的一批,集中上一波分了就。
A
题面太长不看怒吃两发呃呃
分别考虑每个位置,当 或 时该位置必定不合法。当且仅当所有位置均不合法时无解。
复制复制// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long //============================================================= //============================================================= 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); int T = read(); while (T --) { std::string a, b, c, d; int flag = 1, n; std::cin >> n >> a >> b >> c; for (int i = 0; i < n; ++ i) { if (a[i] == c[i] || b[i] == c[i]) continue; flag = 0; } printf("%s\n", (flag == 0) ? "YES" : "NO"); } return 0; }
B
典中典之 ,则组成的三角形合法当且仅当等边,或者等腰且底比腰短。
组合数搞下就好了。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 3e5 + 10; //============================================================= int n, a[kN], cnt[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; } LL C2(int x_) { if (x_ < 2) return 0; return 1ll * x_ * (x_ - 1) / 2ll; } LL C3(int x_) { if (x_ < 3) return 0; return 1ll * x_ * (x_ - 1) / 2ll * (x_ - 2) / 3ll; } //============================================================= int main() { //freopen("1.txt", "r", stdin); int T = read(); while (T --) { n = read(); for (int i = 0; i <= n; ++ i) cnt[i] = 0; for (int i = 1; i <= n; ++ i) { a[i] = read(); cnt[a[i]] ++; } LL ans = 0; int num = 0; for (int i = 0; i <= n; ++ i) { ans += C3(cnt[i]); ans += num * C2(cnt[i]); num += cnt[i]; } printf("%lld\n", ans); } return 0; }
C
不懂为啥这题有个贪心的标签。
对于一次询问显然只会在相邻城市之间移动,仅需求一路上可以减小的代价之和即可。则对于任意询问 ,记 表示从城市 1 移动到城市 一路上可以用第二种旅行减小的代价之和,答案即为 。 的询问同理,预处理 表示从城市 移动到城市 一路上可以用第二种旅行减小的代价之和即可。
求每个城市的最近城市预处理即可,总时空复杂度 级别。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 1e5 + 10; const LL kInf = 2e9 + 2077; //============================================================= int n, m; LL a[kN], sum[2][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(); a[0] = -kInf, a[n + 1] = kInf; sum[0][0] = sum[1][n + 1] = 0; for (int i = 1; i <= n; ++ i) a[i] = read(), sum[0][i] = sum[1][i] = 0; for (int i = 1; i <= n; ++ i) { LL d1 = a[i] - a[i - 1], d2 = a[i + 1] - a[i]; sum[0][i] = sum[1][i] = 0; if (d1 < d2) { sum[1][i] = -d1 + 1; } else { sum[0][i] = -d2 + 1; } } for (int i = 1; i <= n; ++ i) sum[0][i] += sum[0][i - 1]; for (int i = n; i >= 1; -- i) sum[1][i] += sum[1][i + 1]; } //============================================================= int main() { // freopen("1.txt", "r", stdin); int T = read(); while (T --) { Init(); int m = read(); while (m --) { int x = read(), y = read(); LL ans = abs(a[x] - a[y]); if (x < y) { ans += sum[0][y - 1] - sum[0][x - 1]; } else { ans += sum[1][y + 1] - sum[1][x + 1]; } printf("%lld\n", ans); } } return 0; }
D
没调出来妈的,赛后五分钟发现是偷懒直接用置零 表示把 杀了影响到同一轮其他的判断了呃呃,我是飞舞一个好相似、、、
发现当某一轮没有怪物挂掉则之后也不会有任何怪物挂掉;对于每一轮游戏,有可能会挂掉的怪物尽可能是在上一轮挂掉的怪物两侧的怪物。于是直接模拟给定的过程,用链表维护相邻的怪物,并且每一轮仅需考虑在上一轮中挂掉的怪物相邻的怪物即可。
注意某一轮没有怪物挂掉则停止模拟并输出若干个 0。另外注意写法,由规则可知需要在判断完所有怪物是否似掉之后才能修改链表,求下一轮影响到的怪物之前要更新完链表。
总时间复杂度 级别。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 3e5 + 10; //============================================================= int n, a[kN], d[kN], dead[kN]; int pre[kN], next[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() { std::queue <int> q1, q2, q3; int len = 0; for (int i = 1; i <= n; ++ i) q1.push(i); for (int i = 1; i <= n; ++ i) { int ans = 0; while (!q1.empty()) { int x = q1.front(); q1.pop(); if (dead[x] || x <= 0 || x > n) continue; if (d[x] < a[pre[x]] + a[next[x]]) { if (!dead[x]) ++ ans, dead[x] = 1; q2.push(x); } } printf("%d ", ans); ++ len; if (ans == 0) break; while (!q2.empty()) { int x = q2.front(); q2.pop(); q3.push(x); next[pre[x]] = next[x]; pre[next[x]] = pre[x]; } while (!q3.empty()) { int x = q3.front(); q3.pop(); q1.push(pre[x]), q1.push(next[x]); } } for (int i = len + 1; i <= n; ++ i) printf("0 "); printf("\n"); } //============================================================= int main() { //freopen("1.txt", "r", stdin); int T = read(); while (T --) { n = read(); a[0] = a[n + 1] = 0; d[0] = d[n + 1] = 0; for (int i = 1; i <= n; ++ i) a[i] = read(); for (int i = 1; i <= n; ++ i) { dead[i] = 0; pre[i] = i - 1, next[i] = i + 1; } for (int i = 1; i <= n; ++ i) d[i] = read(); Solve(); } return 0; }
E
好玩构造,一开始写了个上限是 900 个数的直接硬吃两发,然后莫名其妙地手玩了下直接出来了,最人类智慧的一集。
首先发现一个长度为 的递增数列中有 个递增子数列(含空数列),于是想到能不能二进制分解 。
一开始的想法是发现有两个递增数列 和 ,且满足 ,则数列 中递增子数列数量即为 ,于是考虑用 凑 ,然而这样搞所需数量上限为 ,过不去呃呃吃了三发
于是开始手玩。一个思考方向是为了最小化所需数量至少要有一个长度为 的递增数列,考虑能否复用这个递增数列中的某些元素来凑出其他 2 的幂出来。发现可以通过在左侧添加一些数来满足上述要求。比如:1 2 3 4
有 个递增子数列,1 1 2 3 4
有 个递增子数列,2 1 2 3 4
有 个递增子数列,2 1 1 2 3 4
有 个递增子数列。
通过上面的例子,构造方案已经呼之欲出了。具体地:首先构造递增数数列 ,对 进行二进制分解后从高位到低位枚举,若第 位为 1 则在数列最左侧添加 。该构造方案至多需要 个数,所以不会出现无解的情况。
另外自带的 log2
函数精度不太够的样子,建议手写。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long //============================================================= LL x, pos[110]; std::stack <LL> ans; //============================================================= 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; } LL mylog2(LL x_) { for (LL i = 62; i >= 0; -- i) { if (x_ >= (1ll << i)) return i; } return 0; } //============================================================= int main() { // freopen("1.txt", "r", stdin); int T = read(); while (T --) { std::cin >> x; while (!ans.empty()) ans.pop(); LL lg = (long long) mylog2(x), now = 0; for (LL i = lg - 1; i >= 0; -- i) pos[lg - 1 - i] = i, ans.push(i); x -= 1ll << lg; for (LL i = lg - 1; i >= 0; -- i) { LL j = 1ll << i; if (x < j) continue; x -= j; ans.push(pos[i]); } printf("%d\n", ans.size()); while (!ans.empty()) printf("%lld ", ans.top()), ans.pop(); printf("\n"); } return 0; }
F
事实上是傻逼恶心区间 DP。
感觉是挺一眼的状态,设 表示将区间 全部修改为 的最小操作次数,为了方便转移另设 表示将区间 全部修改为非 的最小操作次数。初始化:
转移时考虑枚举区间 ,枚举区间要改成的数 ,再枚举区间分界 ,则有:
发现上面两种转移挺对称的,而且发现 的转移会依赖于 ,注意应当先将 转移完后再考虑 。答案即为:
总时间复杂度 级别,空间复杂度 级别。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 110; const int kInf = 1e9 + 2077; //============================================================= int n, x, ans, a[kN]; int f[kN][kN][kN], g[kN][kN][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() { for (int i = 1; i <= n; ++ i) { for (int j = i; j <= n; ++ j) { for (int k = 1; k <= x; ++ k) { f[i][j][k] = g[i][j][k] = kInf; } } } for (int i = 1; i <= n; ++ i) { for (int j = 1; j <= x; ++ j) { f[i][i][j] = 1; g[i][i][j] = 0; } f[i][i][a[i]] = 0; g[i][i][a[i]] = 1; } } void DP() { for (int len = 2; len <= n; ++ len) { for (int l = 1, r = l + len - 1; r <= n; ++ l, ++ r) { for (int k = 1; k <= x; ++ k) { for (int mid = l; mid < r; ++ mid) { f[l][r][k] = std::min(f[l][r][k], f[l][mid][k] + f[mid + 1][r][k]); f[l][r][k] = std::min(f[l][r][k], f[l][mid][k] + g[mid + 1][r][k] + 1); f[l][r][k] = std::min(f[l][r][k], g[l][mid][k] + f[mid + 1][r][k] + 1); f[l][r][k] = std::min(f[l][r][k], g[l][mid][k] + g[mid + 1][r][k] + 1); } } for (int k = 1; k <= x; ++ k) { for (int k1 = 1; k1 <= x; ++ k1) { if (k != k1) g[l][r][k] = std::min(g[l][r][k], f[l][r][k1]); } for (int mid = l; mid < r; ++ mid) { g[l][r][k] = std::min(g[l][r][k], g[l][mid][k] + g[mid + 1][r][k]); g[l][r][k] = std::min(g[l][r][k], g[l][mid][k] + f[mid + 1][r][k] + 1); g[l][r][k] = std::min(g[l][r][k], f[l][mid][k] + g[mid + 1][r][k] + 1); g[l][r][k] = std::min(g[l][r][k], f[l][mid][k] + f[mid + 1][r][k] + 1); } } } } } //============================================================= int main() { //freopen("1.txt", "r", stdin); int T = read(); while (T --) { n = read(), x = read(); for (int i = 1; i <= n; ++ i) a[i] = read(); Init(); DP(); ans = kInf; for (int i = 1; i <= x; ++ i) ans = std::min(ans, f[1][n][i]); printf("%d\n", ans); } return 0; } /* 1 5 3 1 2 3 1 2 */
写在最后
学到了什么:
- B:虽然这题不会溢出……求 的时候最好写成
1ll * x_ * (x_ - 1) / 2ll * (x_ - 2) / 3ll
。 - D:发现每轮会发生改变的元素有限,且总数是确定的,则仅需考虑总复杂度并且每轮对会发生改变的元素进行操作即可。
- E:发现数量与 2 的幂有关考虑二进制分解。
log2
精度不高。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通