AtCoder Grand Contest 003
题目传送门:AtCoder Grand Contest 003。
A - Wanna go back home
显然南北和东西分开考虑,如果南北都没出现,或者都有出现,那必然可以,否则不行,东西同理。
#include <cstdio>
const int MN = 100005;
char s[MN];
int a, b, c, d;
int main() {
scanf("%s", s + 1);
for (int i = 1; s[i]; ++i) {
if (s[i] == 'N') ++a;
if (s[i] == 'S') ++b;
if (s[i] == 'E') ++c;
if (s[i] == 'W') ++d;
}
puts((a && !b) || (b && !a) || (c && !d) || (d && !c) ? "No" : "Yes");
return 0;
}
B - Simplified mahjong
就贪就完事了。
#include <cstdio>
const int MN = 100005;
int N, A[MN];
long long Ans;
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i) scanf("%d", &A[i]);
int lst = 0;
for (int i = 1; i <= N; ++i)
Ans += (A[i] + lst) / 2,
lst = A[i] ? (A[i] + lst) % 2 : 0;
printf("%lld\n", Ans);
return 0;
}
C - BBuBBBlesort!
第二种操作就是交换同奇偶位置上的数。第一种操作就是交换异奇偶位置上的数。
还有 \(A\) 互不相同的条件,那么我们离散化后,直接考虑哪些位置与自己本应所在的位置的奇偶性不同即可,除以 \(2\) 就是答案。
#include <cstdio>
#include <algorithm>
const int MN = 100005;
int N, A[MN], B[MN], Ans;
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i) scanf("%d", &A[i]), B[i] = A[i];
std::sort(B + 1, B + N + 1);
for (int i = 1; i <= N; ++i)
Ans += (std::lower_bound(B + 1, B + N + 1, A[i]) - B - i) & 1;
printf("%d\n", Ans >> 1);
return 0;
}
D - Anticube
很显然我们可以把每个正整数划分到某一类中——质因数分解后每个质数的指数模 \(3\) 都同余的数在同一类。
而且除了 \(1\) 所在类,其它类都是有两两匹配的关系的。\(1\) 所在类最多选一个,其它匹配的类选两类中数量多的更优。
确定每个数属于哪一类以及该类的匹配类就是问题关键,后续部分用 std::map 不难处理。
我们规定某个数 \(x\) 所在的那一类的编号,就是把其所有质因子的指数对 \(3\) 取模后再乘起来的数值。
注意到值域是 \({10}^{10}\),如果每个数都用 \(\sqrt{{10}^{10}}\) 的时间去分解,这是不能接受的。
我们考虑分解 \(\le \sqrt[3]{{10}^{10}}\)(值为 \(2154\))的质因数,并且把这一部分的类编号和与其匹配的类编号都处理出来,这是容易的。
令被分解数为 \(x\),令仅考虑 \(\le \sqrt[3]{{10}^{10}}\) 的质因数时的类编号为 \(y\),令与 \(y\) 匹配的类编号为 \(z\)。
观察此时 \(x\) 仅剩最多两个质因数,且它们都 \(> \sqrt[3]{{10}^{10}}\)。
- 如果 \(x = 1\),则分解已经完成。
- 如果 \(x = p^2\)(这是能够 \(\mathcal O (1)\) 进行检测的,假设
sqrt
的时间是常数),则 \(y\) 乘上 \(p^2\),\(z\) 乘上 \(p\)。 - 如果 \(x \le \sqrt{{10}^{10}}\),则 \(x = p\),则 \(y\) 乘上 \(p\),\(z\) 乘上 \(p^2\)。
- 如果前三种情况均不满足,则说明要么 \(x = p > \sqrt{{10}^{10}}\),要么 \(x = p q\)。此时 \(y\) 乘上 \(x\),然后
这两种情况下 \(z\) 都会乘上 \(> {10}^{10}\) 的数值,已经超过值域范围,所以不可能和任何其他类匹配,直接当作 \(\infty\) 即可。
注意如果任意时刻 \(z\) 超过了 \({10}^{10}\) 的范围,就可以直接不考虑了。这样就可以在 \(\mathcal O \!\left( n \sqrt[3]{{10}^{10}} \right)\) 的复杂度内解决问题。
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <map>
typedef long long LL;
const LL Infll = 0x3f3f3f3f3f3f3f3f;
int N, Ans;
std::map<std::pair<LL, LL>, std::pair<int, int>> mp;
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i) {
LL x, y = 1, z = 1;
scanf("%lld", &x);
for (int j = 2; j <= 2154; ++j) if (x % j == 0) {
int c = 0, d = 0;
while (x % j == 0) x /= j, ++c, d += 2;
c %= 3, d %= 3;
while (c) y *= j, --c;
while (d) z = j <= Infll / z ? z * j : Infll + 1, --d;
}
y *= x;
LL v = sqrt(x);
while (v * v <= x) ++v;
while (v * v > x) --v;
if (v * v == x) z = v <= Infll / z ? z * v : Infll + 1;
else if (x <= 100000) z = x * x <= Infll / z ? z * x * x : Infll + 1;
else z = Infll;
if (y == 1) Ans = 1;
else if (y <= z) ++mp[{y, z}].first;
else ++mp[{z, y}].second;
}
for (auto p : mp) Ans += std::max(p.second.first, p.second.second);
printf("%d\n", Ans);
return 0;
}
E - Sequential operations on Sequence
把 \(N\) 塞进 \(q\) 序列的开头(这会让一些后续讨论更简单)。然后,容易发现如果 \(q_i \ge q_j\)(\(i < j\)),则把 \(q_i\) 删掉是更优的。
这样先用个单调栈把 \(q\) 序列给削成严格递增的情况,记作 \(A\) 数组,假设削完后的长度是 \(Q\)(之前的 \(Q\) 就不用了,代码中是 tp
)。
倒序考虑的想法是很容易想到的,但是搞清楚具体如何实现是有一定难度的。
一开始我们有一个长度为 \(A[1]\) 的序列 \([1, 2, \ldots, A[1]]\),然后每有一个 \(A[i]\) 就把原序列复制拼接。这个过程的倒序是什么呢?
注意到我们最后要求每个数的出现次数,那么考虑维护一个 \(b\) 数组,其中 \(b[x]\) 表示 \(x\) 出现的次数。则最终 \(b\) 就是答案数组。
那么在倒序考虑的时候,假设我们需要考虑 \(A[i : Q]\) 这一部分,\(b\) 数组就定义为假设 \(A\) 就是这个后缀时的答案数组。
显然当 \(i = Q\) 的时候,就有 \(b[1 \sim A[Q]] = 1\),其它都为 \(0\)。
而当 \(i < Q\) 的时候,就有 \(b[x] = b'[x] + b'[x + A[i]] + b'[x + 2 A[i]] + \cdots\),其中 \(b'\) 就是后一个的 \(b\) 数组。
换句话说,当 \(i + 1\) 转移到 \(i\) 时,原有的 \(b'[k]\) 会加到新的 \(b[((k - 1) \bmod A[i]) + 1]\) 上,这个操作是累加的。
我们再观察 \(b'[1 \sim A[Q]] = 1\) 的情况,它转移到 \(A[Q - 1]\) 时,可以发现是有 \(\displaystyle \left\lceil \frac{A[Q]}{A[Q - 1]} \right\rceil\) 段的。
其中 \(\displaystyle \left\lfloor \frac{A[Q]}{A[Q - 1]} \right\rfloor\) 段完整地转移给了 \(b[1 \sim A[Q - 1]]\),让它们的值都加上了 \(\displaystyle \left\lfloor \frac{A[Q]}{A[Q - 1]} \right\rfloor\)。
还剩 \(A[Q] \bmod A[Q - 1]\) 个数,如果这个值非零,那就会让 \(b[1 \sim A[Q] \bmod A[Q - 1]]\) 都加上 \(1\)。
上述分析对于任何一个 \(b'\) 的前缀 \(b'[1 \sim k]\) 都是相同的值的情况下,都是成立的,而且上述变换是「线性变换」。
注意到上述情况把一个 \(b'[1 \sim k]\) 分裂成了两个,直接做的话个数也是指数级增长的。
但是需要特别注意到正是因为它是线性变换,所以我们可以把其中一部分进行拆分或合并,分开在不同时间计算,以简化计算。
注意到一个 \(b'[1 \sim k]\) 拆出来的是两个 \(b[1 \sim A[i]]\) 和 \(b[1 \sim k \bmod A[i]]\),前者是 \(A\) 数组中的值,后者每次变成原来的最多一半。
那么我们此时考虑当 \(i\) 确定,某个 \(b'[1 \sim k]\) 为 \(v\) 时,依次加入 \(A[i - 1], \ldots , A[1]\) 时是如何转移的。
- 找到 \(A[1 \sim (i - 1)]\) 中从后往前数第一个小于等于 \(k\) 的位置 \(A[p]\)。
- 如果找不到,也就是 \(k < A[1]\),那么说明已经转移到头了,直接给最终答案的 \(b[1 \sim k]\) 加上 \(v\) 就行。
- 让当 \(i = p\) 时的 \(b\) 数组的 \(b[1 \sim A[p]]\) 加上 \(\displaystyle v \cdot \left\lfloor \frac{k}{A[p]} \right\rfloor\),由于 \(A\) 是递增的,可以使用二分查找。
- 令 \(k \gets k \bmod A[p]\)。回到第 1 步。
因为 \(k\) 每次变成原来的最多一半,这个过程只会持续 \(\log k\) 步,总时间复杂度为 \(\mathcal O (\log k \log Q)\)。
注意到这个过程只会让某个 \(i\) 时的 \(b\) 数组的 \(b[1 \sim A[i]]\) 加上某个数,也就是说其实 \(b\) 数组的取值都全部相同,直接记作 \(t[i]\) 就行。
不同的取值呢?其实不同的取值已经随着 \(k\) 一直往下掉了,最终直接贡献给答案了。
一开始 \(i = Q\) 时就是 \(t[Q] = 1\)。我们只要从 \(i = Q\) 开始,一直让 \(i\) 减小到 \(2\) 就可以把 \(t[2 \sim Q]\) 全部往下移动到答案数组和 \(t[1]\) 中了。
最终答案数组的 \(1 \sim A[1]\) 这些位置加上 \(t[1]\) 即可。对答案数组的前缀加,显然用后缀和差分优化一下即可。
总时间复杂度为 \(\mathcal O (Q \log q \log Q + N)\)。
#include <cstdio>
#include <algorithm>
typedef long long LL;
const int MN = 100005, MQ = 100005;
int N, Q, tp;
LL A[MQ], t[MQ], Ans[MN];
int main() {
scanf("%d%d", &N, &Q);
if (!Q) {
for (int i = 1; i <= N; ++i) puts("1");
return 0;
}
A[tp = 1] = N;
for (int i = 1; i <= Q; ++i) {
LL x;
scanf("%lld", &x);
while (tp && A[tp] >= x) --tp;
A[++tp] = x;
}
t[tp] = 1;
for (int i = tp; i >= 2; --i) {
LL k = A[i];
t[i - 1] += k / A[i - 1] * t[i], k %= A[i - 1];
while (k >= A[1]) {
int p = std::upper_bound(A + 1, A + tp + 1, k) - A - 1;
t[p] += k / A[p] * t[i], k %= A[p];
}
Ans[k] += t[i];
}
Ans[A[1]] += t[1];
for (int i = N; i >= 1; --i) Ans[i] += Ans[i + 1];
for (int i = 1; i <= N; ++i) printf("%lld\n", Ans[i]);
return 0;
}
F - Fraction of Fractal
如果 \(K = 0\) 输出 \(1\),接下来假设 \(K \ge 1\)。
首先一个很重要的观察是,\(k\) 阶分形的构造,有另一种方式。
题目描述的是,在给出的那个 \(H \times W\) 的网格内,把黑色格子替换成 \(k - 1\) 阶分形,白色格子替换成等大的但全白的网格。
考虑另一种方式:\(k\) 阶分形,是 \(k - 1\) 阶分形中,把黑色格子替换成给出的 \(H \times W\) 的网格,白色格子替换成等大的但全白的网格。
这种方式也是对的,而且对理解接下来的过程有很大帮助。
我们称给出的 \(H \times W\) 的网格是左右相连的,当且仅当存在某一行满足它的第一个和最后一个格子都是黑色的。
这意味着,如果有两个原网格横向拼接在一起,它们必然是连通的。类似地,定义上下相连。
考虑如果原网格既不是左右相连的,又不是上下相连的,那么也就是说不管怎么拼,每个连通块都只不过是原网格的连通块而已。
此时连通块数显然就是总黑色格子数量除以原网格中的黑色格子个数,记原网格黑色格子个数为 \(a\),则答案为 \(a^{K - 1}\)。
如果原网格既是左右相连的,又是上下相连的,用归纳法可得任意阶分形都是一整个连通块,输出 \(1\)。
否则要么是左右相连要么是上下相连,这两种情况是对称的,我们不妨假设是左右相连。如果是上下相连请转置网格。
那么我们考虑 \(K - 1\) 阶分形变成 \(K\) 阶分形的时候,到底会有多少个连通块。
先假设 \(K - 1\) 阶分形中每个黑色格子都变成了一个独立的连通块,当然这是不可能的,因为网格可以左右相连。
那么考虑 \(K - 1\) 阶分形中,一行内的横向相邻的两个黑色格子,它们变成的连通块,在 \(K\) 阶分形中其实是相连的。
也就是说,\(K\) 阶分形的连通块数量,就是 \(K - 1\) 阶分形的黑色格子数量,减去 \(K - 1\) 阶分形的,相邻两个黑色格子对的个数。
黑色格子数量很好数,但是相邻黑色格子对数量怎么计算呢?
为了方便,定义 \(b\) 为原网格中相邻黑色格子对数量,以及 \(c\) 为原网格中的,第一个和最后一个格子都是黑色的行的数量。
我们考虑 \(i\) 阶分形(\(i \ge 1\))的相邻黑色格子对数量,对于 \(i - 1\) 阶分形的每个黑色格子,都贡献 \(b\) 个,而相邻黑色格子也可能位于 \(i - 1\) 阶分形的相邻黑色格子的边界上(它们变成原网格时拼在一起),也就是恰好贡献 \(c\) 个。
而 \(0\) 阶分形,有 \(1\) 个黑色格子,有 \(0\) 个相邻的黑色格子对,那么我们考虑这样的矩阵快速幂:
得出的恰好就是 \(K - 1\) 阶分形的黑色格子个数,和相邻黑色格子对个数,相减得到答案。
#include <cstdio>
typedef long long LL;
const int Mod = 1000000007;
const int MN = 1005;
inline int qPow(int b, LL e) {
int a = 1;
for (; e; e >>= 1, b = (LL)b * b % Mod)
if (e & 1) a = (LL)a * b % Mod;
return a;
}
int N, M, A, B, C; LL K;
char S[MN][MN];
int R[2][2], Q[2][2], tmp[2][2];
int main() {
scanf("%d%d%lld", &N, &M, &K);
if (!K) return puts("1"), 0;
for (int i = 1; i <= N; ++i) {
scanf("%s", S[i] + 1);
for (int j = 1; j <= M; ++j)
if (S[i][j] == '#') ++A;
}
int ok1 = 0, ok2 = 0;
for (int i = 1; i <= N; ++i) if (S[i][1] == '#' && S[i][M] == '#') ++ok1;
for (int j = 1; j <= M; ++j) if (S[1][j] == '#' && S[N][j] == '#') ++ok2;
if (ok1 && ok2) return puts("1"), 0;
if (!ok1 && !ok2) return printf("%d\n", qPow(A, K - 1)), 0;
if (ok1) {
C = ok1;
for (int i = 1; i <= N; ++i)
for (int j = 1; j < M; ++j)
if (S[i][j] == '#' && S[i][j + 1] == '#') ++B;
} else {
C = ok2;
for (int j = 1; j <= M; ++j)
for (int i = 1; i < N; ++i)
if (S[i][j] == '#' && S[i + 1][j] == '#') ++B;
}
Q[0][0] = A, Q[1][0] = B, Q[1][1] = C;
R[0][0] = R[1][1] = 1;
#define F(i) for (int i = 0; i < 2; ++i)
--K;
while (K) {
if (K & 1) {
F(i) F(j) tmp[i][j] = 0;
F(i) F(j) F(k) tmp[i][k] = (tmp[i][k] + (LL)R[i][j] * Q[j][k]) % Mod;
F(i) F(j) R[i][j] = tmp[i][j];
}
F(i) F(j) tmp[i][j] = 0;
F(i) F(j) F(k) tmp[i][k] = (tmp[i][k] + (LL)Q[i][j] * Q[j][k]) % Mod;
F(i) F(j) Q[i][j] = tmp[i][j];
K >>= 1;
}
printf("%d\n", (R[0][0] - R[1][0] + Mod) % Mod);
return 0;
}