AtCoder Grand Contest 003 解题报告

A-Wanna go back home

题目链接

题目简述

高桥君在一个平面上走路N次,且每次走路的方向已经确定,但是距离没有确定。

请确定每次走路的距离 (至少为 \(1\) ) ,使高桥君在这N次走路后可以回到 \((0,0)\)

给你一个字符串 \(S\) ,由字符 N W S E 组成,表示向北/西/南/东行动,输出 Yes 表示高桥君能够回到 \((0,0)\),否则输出 No

  • \(1 \leq |S| \leq 1000\)

解题思路

非常简单,如果高桥君向某个方向行动,且不会向这个方向的反方向行动,则高桥君显然无法回到 \((0, 0)\),否则一定可以。

B - Simplified mahjong

题目链接

题目简述

给定 \(N\),有一组面值为 \(1 \to N\) 的牌,已知每种面值的个数 \(A_i\),卡牌 \(a\) , \(b\)可以组成一对 \((a, b)\) 当且仅当 \(|a面值 - b面值| \leq 1\) 。问最多能组多少对。

  • \(1 \leq N \leq 10^5\)

  • \(1\leq A_i \leq 10^9\)

解题思路

贪心,从小到大扫描,优先和面值相同的配对,如果剩下了一张无法配对就和面值大一的配对。

我这题一开始想错了,某个面值剩下一张的时候并不一定会和大一的面值配对,只有当面值大一的也剩下一张才配对。这种贪心显然没有上面提到的决策优。

考虑一组数据

3
3 2 3

如果使用错误的贪心,在面值为 \(1\) 的牌与面值为 \(2\) 的牌配对时,会造成一张面值为 \(1\) 的牌的浪费,而强行配对不会造成浪费,也不会使答案更劣。

C - BBuBBBlesort!

题目链接

题目简述

给定一个序列 \(A\),保证元素两两不同,你可以对这个序列做以下两种操作。

  1. 翻转相邻两个元素。

  2. 翻转相邻三个元素。

请用这两种操作将序列 \(A\) 变为升序,并使操作 \(1\) 的次数尽可能少。

输出操作 \(1\) 的最少次数。

  • \(1 \leq |A| \leq 10^5\)

  • \(1 \leq A_i \leq 10^9\)

解题思路

其实也不困难,很容易发现使用操作 \(2\) 不能够将原本在第奇数个位置的数换到第偶数个位置上,同样也不能把原本在第偶数个位置上的数换到第奇数个位置上。但是能够将奇数、偶数位置上的数分别任意排列。因此我们只要尽可能把本应该放在奇数/偶数位置上的数放到奇数/偶数位置上即可。

容易发现一次操作 \(1\) 最多能同时把两个数放回对应的奇数/偶数位置,并且由于我们可以用操作 \(2\) 将奇数/偶数位置上的数分别任意排列,因此我们一定能够做到用一次操作 \(1\) 复位两个数,故我们只要统计有多少个原本应该在奇数/偶数位置上的数没有被放在奇数/偶数位置上,然后除以二就是答案。

参考代码

#include <vector>
#include <cctype>
#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;
using LL = long long;

int n;
vector<int> nums;
int a[100010], b[100010];

int main(void)
{
	cin >> n;
	for (int i = 0; i < n; ++i) {
		cin >> a[i];
		nums.emplace_back(a[i]);
	}
	sort(nums.begin(), nums.end());
	for (int i = 0; i < n; ++i)
		a[i] = lower_bound(nums.begin(), nums.end(), a[i]) - nums.begin();
	int cnt = 0;
	for (int i = 0; i < n; ++i) {
		if (i % 2 != a[i] % 2) ++cnt;
	}
    cout << cnt / 2 << endl;
	return 0;
}

D - Anticube

题目链接

题目简述

给定 \(n\) 个数 \(s_i\),要求从中选出最多的数,满足任意两个数之积都不是完全立方数。

  • \(1 \leq n \leq 10^5\)

  • \(1 \leq s_i \leq 10^{10}\)

没写出来,似乎是一道阴间数学题。

E - Sequential operations on Sequence

题目链接

题目简述

一串数,初始为\(1 \to N\) ,现在给 \(Q\) 个操作,每次操作把数组长度变为 \(q_i\),新增的数为上一个操作后的数组的重复。问 \(Q\) 次操作后 \(1 \to N\) 每个数出现了多少次。

  • \(1 \leq N \leq 10^5\)
  • \(0 \leq Q \leq 10^5\)
  • \(1 \leq q_i \leq 10^{18}\)

解题思路

今天的重头戏

首先我们很容易想到,虽然这个数组初始为 \(1 \to N\),但是如果存在 \(q_i < N\),并且对于 \(\forall j < i\)\(q_j >= q_i\),就会导致前面的一系列操作都是不用管的,到了第 \(i\) 步这个数组一定会变成 \(1\to q_i\)

这个结论其实没用,但是很有启发性。于是我们很快又可以发现另一个性质:对于一段连续的操作 \(q_i,\ i \in [l,r]\) 如果对于 \(\forall i \in [l, r],\ q_i >= q_r\) ,那么这一段连续的操作与操作 \(q_r\) 等效

这个性质说明了如果一个操作小于前面的一系列操作,前面的一系列操作可以不做。

这个性质很有用,它说明我们实际要进行的操作 \(q_i\) 是单调递增的,也就是说我们不用管这个数组的缩短,只用计算它的伸长就好了。

我们把初始的 \(N\) 也看作是一次操作,用单调栈或者倒序处理就能维护出一个单调递增的操作序列 \(Q'\)

接下来我们考虑这些伸长的操作。初始数组长度为 \(q_0\),我们现在要进行操作将其伸长到 \(q_1\),显然原始数组重复了 \(\lfloor \cfrac{q_1}{q_0} \rfloor\) 次,并且后接一段长度为 \(q_1 \bmod q_0\) 的数组。

如果我们用 \(a_0\) 来表示初始数组,\(b_0\) 表示后面接的数组,那么就有

\[a_1 = \lfloor \cfrac{q_1}{q_0} \rfloor \times a_0 + b_0 \]

然后就有

\[a_2 = \lfloor \cfrac{q_2}{q_1}\rfloor \times a_1 + b_1 \]

接下来得出

\[a_2 = \lfloor \cfrac{q_2}{q_1}\rfloor \times (\lfloor \cfrac{q_1}{q_0} \rfloor \times a_0 + b_0) + b_1 \]

如是 \(|Q'|\) 遍,最终就有

\[a_n = \lfloor \cfrac{q_n}{q_{n - 1}} \rfloor \times(\lfloor \cfrac{q_{n - 1}}{q_{n - 2}}\rfloor \times (\cdot\cdot\cdot) +b_{n - 2}) + b_{n - 1} \]

看起来非常复杂,我们尝试从外往里做。

我们用数组 f 记录数组 \(a_i\) 出现的次数(也就是它前面的系数),显然我们可以一层一层把式子拆开,把系数乘起来。

然而我们发现这坨式子中的 \(b_i\) 简直丧心病狂,我们重点讨论如何计算它。

由于 \(b_i\) 是重复的产物(并且是不完整的一段),显然如果 \(b_i\) 的长度是 \(l_i\),那么它就会等于整个数组最前面的 \(l_i\) 个数。

又由于整个数组是重复的产物,所以整个数组最前面的 \(l_i\) 个数也是某个数组重复的产物。是哪个数组呢?我们二分查找第一个小于等于 $ l_i$ 的 \(q_j\)\(b_i\) 就是由 \(q_j\) 对应的数组(也就是 \(a_j\)) 重复产生的。于是 \(a_j\) 又多出现了 \(\lfloor \cfrac{l_i}{q_j}\rfloor\) 次,也即它的系数需要加上 \(\lfloor\cfrac{l_i}{q_j}\rfloor\) 。但是我们发现 \(b_i\) 的末尾应该也会剩下一个无法用完整的 \(a_j\) 表示的数组,我们用和处理 \(b_i\) 一样的方法递归处理即可。当这个数组的长度小于 \(n\) 时,假设它的长度时 \(m\),这个数组实际上就是 \(1 \to m\)。也就是说 \(1 \to m\) 又出现了 \(k\) 次(\(k\)\(b_i\) 对应的系数),给区间 \([1,m]\) 整个加上 \(k\) 即可,区间加法可以用差分数组维护。

最后,除了一大堆 \(b\) 之外,整个式子还会剩下一个 \(a_0\)。但是 \(a_0\) 我们是已知的,就是 \(1 \to q_0\),我们乘上系数,给区间 \([1,q_0]\) 做区间加法,最后前缀和统计答案即可。

参考代码

#include <vector>
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>

using namespace std;
using LL = long long;

const int N = 1e5 + 10, M = 17;

int n, q;
LL a[N];
LL f[N];
LL c[N];
LL minx[N];
vector<LL> len;
vector<LL> na;

void Solve(LL x, LL times)
{
	if (x <= na[0]) {
		c[0] += times;
		c[x + 1] -= times;
	}
	else {
		int t = upper_bound(na.begin(), na.end(), x) - na.begin() - 1;
		f[t] += x / na[t] * times;
		Solve(x % na[t], times);
	}
}

int main(void)
{
	int on;
    cin >> n >> q;
	on = n;
	int start = 1;
	for (int i = 1; i <= q; ++i) {
		io >> a[i];
		if (a[i] < n) {
			n = a[i];
			start = i + 1;
		}
	}
	minx[q] = q;
	for (int i = q - 1; i >= 1; --i) {
		if (a[i] < a[minx[i + 1]])
			minx[i] = i;
		else minx[i] = minx[i + 1];
	}
	na.emplace_back(n);
	for (int i = start, j; i <= q; i = j + 1) {
		j = minx[i];
		na.emplace_back(a[j]);
	}
	f[na.size() - 1] = 1;
	for (int i = na.size() - 1; i >= 1; --i) {
		f[i - 1] += na[i] / na[i - 1] * f[i];
		Solve(na[i] % na[i - 1], f[i]);
	}
	c[0] += f[0]; c[n + 1] -= f[0];
	for (int i = 1; i <= on; ++i) {
		c[i] += c[i - 1];
        cout << c[i] << endl;
	}
	return 0;
}

F - Fraction of Fractal

题目链接

没看,不会做

posted @ 2021-11-05 22:38  LroseC  阅读(101)  评论(0)    收藏  举报