Educational Codeforces Round 161 (Rated for Div. 2)

写在前面

比赛地址:https://codeforces.com/contest/1922

D 没调出来亏炸了,第一次体验赛后五分钟过题的快感。

痛苦的大二上终于结束了,本学期一半的痛苦都来自于傻逼大物实验。

下学期课少了好多,而且早八和晚八都少的一批,集中上一波分了就。

A

题面太长不看怒吃两发呃呃

分别考虑每个位置,当 ai=cibi=ci 时该位置必定不合法。当且仅当所有位置均不合法时无解。

复制复制
//
/*
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

典中典之 2i1+2i1=2i,则组成的三角形合法当且仅当等边,或者等腰且底比腰短

组合数搞下就好了。

//
/*
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

不懂为啥这题有个贪心的标签。

对于一次询问显然只会在相邻城市之间移动,仅需求一路上可以减小的代价之和即可。则对于任意询问 x<y,记 sum0(i) 表示从城市 1 移动到城市 i+1 一路上可以用第二种旅行减小的代价之和,答案即为 ayaxsum0(y1)sum0(x1)x>y 的询问同理,预处理 sum1(i) 表示从城市 n 移动到城市 i1 一路上可以用第二种旅行减小的代价之和即可。

求每个城市的最近城市预处理即可,总时空复杂度 O(n+m) 级别。

//
/*
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

没调出来妈的,赛后五分钟发现是偷懒直接用置零 ai 表示把 i 杀了影响到同一轮其他的判断了呃呃,我是飞舞一个好相似、、、

发现当某一轮没有怪物挂掉则之后也不会有任何怪物挂掉;对于每一轮游戏,有可能会挂掉的怪物尽可能是在上一轮挂掉的怪物两侧的怪物。于是直接模拟给定的过程,用链表维护相邻的怪物,并且每一轮仅需考虑在上一轮中挂掉的怪物相邻的怪物即可。

注意某一轮没有怪物挂掉则停止模拟并输出若干个 0。另外注意写法,由规则可知需要在判断完所有怪物是否似掉之后才能修改链表,求下一轮影响到的怪物之前要更新完链表。

总时间复杂度 O(n) 级别。

//
/*
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 个数的直接硬吃两发,然后莫名其妙地手玩了下直接出来了,最人类智慧的一集。

首先发现一个长度为 n 的递增数列中有 2n 个递增子数列(含空数列),于是想到能不能二进制分解 X

一开始的想法是发现有两个递增数列 a1anb1bm,且满足 a1>bm,则数列 a1an,b1bm 中递增子数列数量即为 (2n1)+(2m1)+1,于是考虑用 2x1X1,然而这样搞所需数量上限为 i=1log2Xi103,过不去呃呃吃了三发

于是开始手玩。一个思考方向是为了最小化所需数量至少要有一个长度为 log2X 的递增数列,考虑能否复用这个递增数列中的某些元素来凑出其他 2 的幂出来。发现可以通过在左侧添加一些数来满足上述要求。比如:1 2 3 424 个递增子数列,1 1 2 3 424+23 个递增子数列,2 1 2 3 424+22 个递增子数列,2 1 1 2 3 424+23+22 个递增子数列。

通过上面的例子,构造方案已经呼之欲出了。具体地:首先构造递增数数列 0,1,,log2X1,对 X 进行二进制分解后从高位到低位枚举,若第 i(0i<log2X) 位为 1 则在数列最左侧添加 log2X1i。该构造方案至多需要 2×log2X1120 个数,所以不会出现无解的情况。

另外自带的 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。

感觉是挺一眼的状态,设 fl,r,k 表示将区间 [l,r] 全部修改为 k 的最小操作次数,为了方便转移另设 gl,r,k 表示将区间 [l,r] 全部修改为 k 的最小操作次数。初始化:

{fi,i,ai=0fi,i,k=1(kai)fl,r,k=(lr)

{gi,i,ai=1gi,i,k=0(kai)gl,r,k=(lr)

转移时考虑枚举区间 [l,r],枚举区间要改成的数 k,再枚举区间分界 m,则有:

{fi,j,kfi,m,k+fm+1,r,kfi,j,kfi,m,k+gm+1,r,k+1fi,j,kgi,m,k+fm+1,r,k+1fi,j,kgi,m,k+gm+1,r,k+1

{gi,j,kfi,j,k(kk)gi,j,kgi,m,k+gm+1,r,k+1gi,j,kgi,m,k+fm+1,r,k+1gi,j,kfi,m,k+gm+1,r,k+1gi,j,kfi,m,k+fm+1,r,k+1

发现上面两种转移挺对称的,而且发现 g 的转移会依赖于 f,注意应当先将 f 转移完后再考虑 g。答案即为:

min1kxf1,n,k

总时间复杂度 O(n4) 级别,空间复杂度 O(n3) 级别。

//
/*
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:虽然这题不会溢出……求 C(x,3) 的时候最好写成 1ll * x_ * (x_ - 1) / 2ll * (x_ - 2) / 3ll
  • D:发现每轮会发生改变的元素有限,且总数是确定的,则仅需考虑总复杂度并且每轮对会发生改变的元素进行操作即可。
  • E:发现数量与 2 的幂有关考虑二进制分解。log2 精度不高。
posted @   Luckyblock  阅读(124)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示