【赛后小结】Codeforces Round #786 (Div. 3)

比赛相关信息

比赛信息

比赛名称: Codeforces Round #786 (Div. 3)
比赛地址: Codeforce


部分题解与小结

C - Infinite Replacement

小评

挺水的构造题,要注意分类讨论,错了一次就是因为讨论的时候较为混乱。

题意

\(T(1 \le T \le 10^4)\) 组样例。给出一串只含 \(\tt a\) 的字符串 \(S\) ,以及一条只含小写字母的字符串 \(T\) ,两者长度均小于 \(50\) 。规定操作:

  • 每次从 \(S\) 种选择一个 \(\tt a\) ,将其替换为 \(T\)

无限进行操作,询问可以获得的不同的 \(S'\) 的数量,若为无限,输出 \(-1\)

思路

易得以下规律

  • \(T\) 中含 \(\tt a\)\(Len_T \neq 1\),即为 \(-1\)
  • \(T\)\(\tt a\) ,即为 \(1\)
  • 只要 \(T\) 中不含 \(\tt a\) ,即为 \(2^{Len_S}\)

AC代码

点击查看代码
void Solve() {
    string s, t; cin >> s >> t;
    if (t == "a") cout << 1 << endl;
    else if (t.find('a') != -1) cout << -1 << endl;
    else cout << (1LL << s.sz) << endl;
}





D - A-B-C Sort

小评

初见以为是很难的找规律题,于是果断跳。做完 \(\tt E\) 回来推了推规律,发现不难,三十分钟就直接切掉了。

题意

\(T(1 \le T \le 2 * 10^ 4)\) 组样例。给出三个数组 \(A,B,C\) ,初始 \(A\) 数组中有 \(N\) 个元素,\(B,C\) 均为空。规定如下操作:

  • \(A\) 不为空时,每一步都取出 \(A\) 末尾的元素并放置到 \(B\)中间位置:特别的,当 \(B\) 中含奇数个元素时,你可以任意放置到中间位置的左侧或者右侧;
  • \(A\) 为空但 \(B\) 不为空时,每一步都取出 \(B\) 中间的元素并放置到 \(C\)末尾位置:特别的,当 \(B\) 中含偶数个元素时,你可以任意选择中间位置的左侧或者右侧的元素;

给出 \(A\) 中的初始元素,询问经上述操作后能否在 \(C\) 中得到一个非递减的序列。

思路

假设 \(A\) 中序列为 \(\tt abcde\) ,操作如下:

\[\tt \bar e \Rightarrow \left\{\begin{matrix} \tt \bar de \Rightarrow d \bar ce \Rightarrow \left\{\begin{matrix} \tt d \bar bce \Rightarrow db \bar ace \\ \tt dc \bar be \Rightarrow dc \bar abe \end{matrix}\right. \\ \tt e \bar d \Rightarrow e \bar cd \Rightarrow \left\{\begin{matrix} \tt e \bar bcd \Rightarrow eb \bar acd \\ \tt ec \bar bd \Rightarrow ec \bar abd \end{matrix}\right. \end{matrix}\right.\]

至此,我们可以得到,当 \(N\) 为奇数时:

  • 第一位 \(\tt a\)
  • 第二位 \(\tt c、b\)
  • 第三位 \(\tt d、e\)

即有规律:当 \(N\) 为奇数时,第一位取出的一定是 \(A\) 中最后一个放进去的,而此后每一位均有两种不同的情况。相对应的,我们也可以得到 \(N\) 为偶数时的情况,根据规律直接输出即可。

自己思路

构建排序过后的 \(A\) 数组 \(A'\) ,按以下方式进行模拟:

  • 对于奇数,特判 \(A[1]\) 是否等于 \(A'[1]\) ,之后两两比对;
  • 对于偶数,直接两两比对;
蒋老师思路

由上述规律可以进一步抽象得到规律:从 \(A\)\(C\) 的操作即将 \(A\) 中元素进行了如下的排序:

\[\left\{\begin{matrix} A[1] & min(A[2],A[3]),max(A[2],A[3]),min(A[4],A[5]),max(A[4],A[5]),… & N为奇 \\ & min(A[1],A[2]),max(A[1],A[2]),min(A[3],A[4]),max(A[3],A[4]),… & N为偶 \end{matrix}\right.\]

故我们只需要按此规律进行排序,再判断排序过后的 \(A\) 数组是否非递减即可。这里我们可以使用到一个函数 is_sorted ,函数原型如下:

bool is_sorted(iterator begin, iterator end);

AC代码

点击查看代码
自己思路
int a[N];
void Solve() {
    int n; cin >> n;
    FOR (i, 1, n) {
        cin >> a[i];
        b[i] = a[i];
    }
    
    sort(b + 1, b + 1 + n);
    int flag = 0;
    if (n % 2 == 1) {
        if (a[1] != b[1]) flag = 1;
        FOR2 (i, 2, n) {
            if ((a[i] == b[i] && a[i + 1] == b[i + 1]) || (a[i] == b[i + 1] && a[i + 1] == b[i]) );
            else flag = 1;
        }
    }else {
        FOR2 (i, 1, n) {
            if ((a[i] == b[i] && a[i + 1] == b[i + 1]) || (a[i] == b[i + 1] && a[i + 1] == b[i]) );
            else flag = 1;
        }
    }
    if (flag) NO;
    else YES;
}
蒋老师思路
int a[N];
void Solve() {
    int n; cin >> n;
    for (int i = 1; i <= n; ++ i) cin >> a[i];
    for (int i = n - 1; i >= 1; i -= 2) {
        if (a[i] > a[i + 1]) swap(a[i], a[i + 1]);
    }
    if (is_sorted(a + 1, a + 1 + n)) YES;
    else NO;
}





E - Breaking the Wall

小评

赛时跳过 \(\tt D\) 直接来做的 \(\tt E\) ,结果发现跟 \(\tt D\) 一样也是一道找规律题,但因为没有上一题这么数学,所以还算能接受(虽说找完规律之后发现这道题确实比上一题要难上这么一些的),但是需要仔细的进行分类讨论。

题意

地上有 \(N\) 堵墙,每一堵都有生命值,你需要操作大炮进行攻击,攻击伤害如下:

  • 假设你选择第 \(i\) 堵墙,那么会对 \(i\) 号墙造成 \(2\) 点伤害,会对 \(i-1\)\(i+1\) 号墙造成 \(1\) 点伤害;

现在需要将两堵墙的生命值攻击至低于 \(0\) ,请你输出最少需要的攻击次数。

思路

由于需要两堵墙的生命值低于 \(0\) ,故对于这两堵墙的选择存在显然的贪心策略:

  • \(i\)\(i+1\)
  • \(i-1\)\(i+1\)
  • 对生命值最少的两堵墙;

记生命值较小的墙的生命值为 \(x\) ,较大的为 \(y\) ,按照上述贪心策略进一步思考:

  • 选择 \(i\)\(i+1\)
    • \(x \le \lfloor \frac{y}{2} \rfloor\) ,答案为 \(\lceil \frac{y}{2} \rceil\)
    • 否则,设分别攻击 \(K_1,K_2\) 下,则有 \(\left\{\begin{matrix} 2K_1+K2 \ge x \\ 2K_2 + K_1 \ge y\end{matrix}\right.\) ,联立易得解 \(K_1+K_2 \ge \frac{x+y}{2}\) ,答案为 \(\lceil \frac{x+y}{2} \rceil\)
  • 选择 \(i-1\)\(i+1\)
    • 首先攻击 \(i\) 直到 \(x \le 0\) ,随后剩下那堵墙直到 \(y \le 0\) ,答案为 \(x+\lceil \frac{y-x}{2} \rceil\)
  • 对生命值最少的两堵墙

最后输出上述答案的最小值即可。

AC代码

点击查看代码
int t[N];
void Solve() {
	int n; cin >> n;
	FOR (i, 1, n) cin >> t[i];
	
	int ans = INFF;
	FOR (i, 2, n) {
		int x = min(t[i - 1], t[i]);
		int y = max(t[i - 1], t[i]);
		if (x <= y / 2) cmin(ans, (y + 1) / 2);
		else cmin(ans, (x + y + 2) / 3);
	}
	FOR (i, 3, n) {
		int x = min(t[i - 2], t[i]);
		int y = max(t[i - 2], t[i]);
		cmin(ans, x + (y - x + 1) / 2);
	}
	sort(t + 1, t + 1 + n);
	cmin(ans, (t[1] + 1) / 2 + (t[2] + 1) / 2);
	cout << ans;
}





F - Desktop Rearrangement

小评

拿到这道题的时候还有相当的时间,在 看完题意之后快速的联想到了“使用线段树进行“单点修改”,确定思路无误之后便直接开码,结果码到一半编译器吃不住压力,自动纠错炸掉了,随之电脑黑屏,于是强制重启了一次。

在还剩余20分钟时交了第一发,错误的原因是结构体存线段树忘记开四倍空间

非常快速的找到错误后又交了第二发,超时第42个点,想了很久为什么,最后发现错误的原因是不是所有的查询都需要使用到线段树(绝大多数查询通过一个数组即可完成);

在还剩余2分钟的时候交了第三发,结果错了第42个点,直到比赛结束没能找到为什么。赛后发现错误的原因是数组越界,如下图

但是这个提示也不是每一次都有,就很鬼畜。详细分析后发现原因在于结构体四倍空间开的还不够大,这就更鬼畜了……

题意

想象电脑桌面的图标排列,我们使用 * 代表图标,. 代表空格。现在有一个大小为 \(N*M(1 \le N,M \le 10^4)\) 的电脑桌面,当所有的图标是从左向右、从上向下排列的,这样的桌面我们称之为”干净的“。我们每一步操作都可以将任意一个图标移动到任意一个空格上去。现在给出 \(Q(1 \le Q \le 2 * 10^5)\) 次询问,每次询问需要进行的操作如下:

  • 给出坐标 \((x,y)\) ,若这一格上有图标,则删除;反之,则新建一个图标;
  • 每次修改都是长期延续的;

对于每一个询问,分别输出让桌面达到”干净的“还需要进行几步操作。

思路

使用一个变量 \(Sum\) 储存桌面上的图标数量,显然,需要输出的答案即为从左到右、从上到下的桌面上的前 \(Sum\) 个格子中空格的数量。

自己思路(线段树/树状数组):

对每一列分别建立一棵线段树——将每次询问想象成线段树的单点修改,将每次输出想象成线段树的区间查询,得解。但是这里有一个优化,即在输出时,对于完整的列,我们可以直接使用一个数组来统计空格数,而只需要对不完整的列进行区间查询即可,复杂度由 \(\mathcal O\Big((N+Q*M)*logN \Big)\) 优化至 \(\mathcal O\Big((N+Q)*logN \Big)\) ,可解。

别的大佬的思路(规律):

将桌面从左至右一列一列的进行拆分,压缩为一个一维结构,首先计算初始状态时达到“干净的”需要的步骤数量,此后,对于每一次询问分类讨论:

  • 若将某一格从空格修改至图标,

    • 新扩入填充区间的格子若为图标,则操作数减一;
    • 若这一格位于需要填充的区间外,则操作数加一;
  • 若将某一格从图标修改至空格,

    • 新从填充区间删去的格子若为图标,则操作数加一;
    • 若这一格位于需要填充的区间外,则操作数减一;

AC代码

点击查看代码
char a[N];
int num, ans;
void Solve() {
	int n, m, q; cin >> n >> m >> q;
	FOR (i, 1, n) FOR (j, 0, m - 1) {
		cin >> a[i + j * n];
		if (a[i + j * n] == '*') ++ num;
	}
	FOR (i, 1, num) if (a[i] == '.') ++ ans;
	FOR (t, 1, q) {
		int x, y; cin >> x >> y;
		int z = x + (y - 1) * n;
		if (a[z] == '.') {
			++ num;
			if (a[num] == '*') -- ans;
			if (z > num) ++ ans;
			a[z] = '*';
		}else {
			if (a[num] == '*') ++ ans;
			-- num;
			if (z > num) -- ans;
			a[z] = '.';
		}
		cout << ans << endl;
	}
}



文 / WIDA
2022.05.12 成文
首发于WIDA个人博客,仅供学习讨论


posted @ 2022-05-12 14:43  hh2048  阅读(31)  评论(0编辑  收藏  举报