AtCoder Grand Contest 016
题目传送门:AtCoder Grand Contest 016。
A - Shrinking
枚举最终得到的字符 \(c\),则每次操作可以被看作是 \(c\) 字符在字符串中往前拓展了一位。
对于字符串长度会减小的问题,我们可以看作 \(s\) 的末尾增加了一个万能字符。
那么一个字符 \(c\) 覆盖整个串的回合数,就是每个字符 \(c\) 之前非 \(c\) 连续段长度的最大值。
扫一遍判断即可。
#include <cstdio>
#include <cstring>
#include <algorithm>
const int MN = 105, Sig = 26;
int N;
char S[MN];
int lst[Sig], ans[Sig];
int main() {
scanf("%s", S + 1), N = strlen(S + 1);
for (int i = 1; i <= N; ++i) {
int x = S[i] - 'a';
ans[x] = std::max(ans[x], i - lst[x] - 1);
lst[x] = i;
}
int tans = N;
for (int x = 0; x < Sig; ++x) tans = std::min(tans, std::max(ans[x], N - lst[x]));
printf("%d\n", tans);
return 0;
}
B - Colorful Hats
我们考虑帽子颜色序列,观察能得到什么样子的合法 \(a\) 序列。
令不同的颜色数为 \(S\),则一只猫 \(i\) 能看到的颜色数为 \(S\) 减去「它是否戴着单独一种颜色的帽子」。
我们称戴着单独一种颜色的帽子的猫是形单影只的,反之则是成群结队的。
容易发现形单影只的猫的 \(a_i = S - 1\),而成群结队的猫的 \(a_i = S\)。
所以 \(a_i\) 的最大值和最小值之差应该恰好为 \(1\) 吗。并不是,但仅有两种例外:
- 如果所有猫都是形单影只的:所有 \(a_i\) 都应该相等,并且都等于 \(S - 1\)。
- 如果所有猫都是成群结队的:所有 \(a_i\) 都应该相等,并且都等于 \(S\),由于每种颜色对应至少两只猫,所以还需保证 \(2 S \le N\)。
否则 \(a_i\) 的最大值和最小值之差应该恰好为 \(1\),则 \(S\) 等于最大值。
那么形单影只的猫的个数即为 \(a_i = S - 1\) 的 \(i\) 的个数,令个数为 \(C\)。
则这些猫占用了 \(C\) 种不同的颜色,还有其它 \(S - C\) 种颜色要被分配给成群结队的猫。
所以成群结队的猫的数量必须多于 \(2 (S - C)\),也就是 \(N - C \ge 2 (S - C)\),也就是 \(C < S \le \left\lfloor \frac{N + C}{2} \right\rfloor\)。
判断上述条件即可。
#include <cstdio>
#include <algorithm>
const int MN = 100005;
int N, A[MN];
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i) scanf("%d", &A[i]);
int mx = *std::max_element(A + 1, A + N + 1);
int mn = *std::min_element(A + 1, A + N + 1);
if (mx == mn) {
if (mx == N - 1) puts("Yes");
else if (mx > N / 2) puts("No");
else puts("Yes");
return 0;
}
if (mx - mn >= 2) return puts("No"), 0;
int cnt = 0;
for (int i = 1; i <= N; ++i) if (A[i] == mn) ++cnt;
puts(mx <= cnt || mx > cnt + (N - cnt) / 2 ? "No" : "Yes");
return 0;
}
C - +/- Rectangle
如果 \(H\) 能被 \(h\) 整除且 \(W\) 能被 \(w\) 整除,显然输出 No
即可。
否则如果我们只把在 \(h\) 的倍数行且在 \(w\) 的倍数列的地方赋成负数,并且它与它左上方 \(h \times w\) 的矩阵中其它 \(h w - 1\) 个格子对应。
则会有一些填上正数的格子未被对应,我们在所有填正数的格子里填上 \(v\),在负数的格子里填上 \(-(h w - 1) v - 1\)。
这样就能保证每个 \(h \times w\) 的子矩阵内的值之和都是恰好 \(-1\)。
而总权值和应为 \(\displaystyle v (H W - (H - H \bmod h) (W - W \bmod w)) - \left\lfloor \frac{H}{h} \right\rfloor \left\lfloor \frac{W}{w} \right\rfloor\)。
与 \(v\) 相乘的那一项是不为 \(0\) 的,容易证明在本题限制和 \({10}^9\) 的值域下,将 \(v\) 取得尽量大,一定能让上述式子大于 \(0\)。
#include <cstdio>
int N, M, a, b;
int main() {
scanf("%d%d%d%d", &N, &M, &a, &b);
if (N % a == 0 && M % b == 0) return puts("No"), 0;
puts("Yes");
int v = 999999999 / (a * b - 1);
for (int i = 1; i <= N; ++i)
for (int j = 1; j <= M; ++j)
printf("%d%c", i % a == 0 && j % b == 0 ? -(a * b - 1) * v - 1 : v, " \n"[j == M]);
return 0;
}
D - XOR Replace
令初始的异或和为 \(x\),拿在手中,相当于每次用手中的数把 \(a\) 中的一个值顶掉,然后把原来的值拿在手里。
那么可以先判一下 \(b\) 是否一定能被得到:只需判 \(b\) 的可重集是否包含于 \(a \cup \{x\}\) 即可。
然后我们考虑一个过程,用 \(x\) 替换了 \(a_p\),然后用 \(a_p\) 替换了 \(a_q\),循环下去。
其实最终一定是要用 \(b_i\) 替换 \(a_i\) 的,而上面的过程又是从 \(x\) 出发走了一条路。
这启发我们从 \(b_i\) 向 \(a_i\) 连边(不同位置上相同的数值对应同一个点),然后尝试从 \(x\) 出发遍历每条边。
注意如果图是一个包含 \(x\) 的连通块,则一定可以找到一条欧拉路径(不一定是回路)覆盖所有边。
如果图不连通,或 \(x\) 不在连通块内(\(x\) 是孤立点),则答案就是边数再加上连通块数再减去 \(1\)(如果 \(x\) 是孤立点就不用减 \(1\))。
#include <cstdio>
#include <vector>
#include <map>
const int MN = 100005;
int N, A[MN], B[MN], X, cnt, M, Ans;
std::map<int, int> mp;
int vis[MN];
std::vector<int> G[MN];
void DFS(int u) {
vis[u] = 1;
for (int v : G[u]) if (!vis[v]) DFS(v);
}
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i) scanf("%d", &A[i]), ++mp[A[i]], X ^= A[i];
for (int i = 1; i <= N; ++i) scanf("%d", &B[i]), --mp[B[i]];
++mp[X];
for (auto p : mp) if (p.second < 0) return puts("-1"), 0;
for (auto &p : mp) p.second = ++cnt;
for (int i = 1; i <= N; ++i) if (A[i] != B[i]) {
int u = mp[B[i]], v = mp[A[i]];
G[u].push_back(v);
G[v].push_back(u);
++M;
}
for (int i = 1; i <= cnt; ++i) if (!G[i].empty())
if (!vis[i]) ++Ans, DFS(i);
if (!G[mp[X]].empty()) --Ans;
printf("%d\n", M + Ans);
return 0;
}
E - Poor Turkeys
我的做法好复杂,但是却取得了更优的理论复杂度。
注意如果我们为每个时间点的每只火鸡都建立一个布尔变量表示它是否还活着。
则是可以根据操作建立一个 2-SAT 模型的,点数是 \(\mathcal O (N M)\) 级别。
不过如果对同一只火鸡,把对它无操作的时刻前后的两个相邻布尔变量合成一个也无妨,所以点数可以是 \(\mathcal O (N + M)\)。
然后跑正常的 2-SAT 过程,缩点建出 DAG 并处理拓扑序。
两只火鸡最后都能活着,当且仅当它们俩首先最后都不是必死的,并且一只活着不会导致另一只死亡。
也就是最终时刻对应的两个布尔变量,一个为 \(1\) 不能导出另一个为 \(0\)。
这也就是要求 DAG 上的可达性问题,因为询问是否能到达的点只有 \(\mathcal O (N)\) 个所以直接 bitset 做就好。
这样复杂度是 \(\displaystyle \mathcal O \!\left( \frac{(N + M) N}{w} \right)\),其中 \(w\) 是字长。
#include <cstdio>
#include <algorithm>
#include <vector>
#include <bitset>
const int MN = 405, MM = 100005;
const int MC = (MM * 2 + MN) * 2;
int N, M, C;
std::vector<int> V[MN], G[MC], G2[MC];
inline int Alive(int x) { return 2 * x - 1; }
inline int Dead(int x) { return 2 * x; }
inline int T(int x) { return ((x - 1) ^ 1) + 1; }
inline void Insert(int x, int y) {
G[x].push_back(y);
G[T(y)].push_back(T(x));
}
int dfn[MC], low[MC], dfc;
int stk[MC], tp, instk[MC];
int bel[MC], scnt;
void Tarjan(int u) {
dfn[u] = low[u] = ++dfc;
instk[stk[++tp] = u] = 1;
for (int v : G[u]) if (!dfn[v]) {
Tarjan(v);
low[u] = std::min(low[u], low[v]);
} else if (instk[v]) low[u] = std::min(low[u], dfn[v]);
if (low[u] == dfn[u]) {
++scnt;
for (int x = 0; x != u; --tp)
bel[x = stk[tp]] = scnt, instk[x] = 0;
}
}
int vis[MC], key[MC];
std::bitset<MN> bit[MC];
int bdfs[MN]; // be dead for sure
int main() {
scanf("%d%d", &N, &M);
for (int i = 1; i <= N; ++i) V[i].push_back(++C);
for (int i = 1, x, y; i <= M; ++i) {
scanf("%d%d", &x, &y);
int u0 = V[x].back(), v0 = V[y].back(), u1, v1;
V[x].push_back(u1 = ++C);
V[y].push_back(v1 = ++C);
Insert(Alive(u1), Alive(u0));
Insert(Alive(v1), Alive(v0));
Insert(Alive(u1), Dead(v1));
Insert(Dead(u0), Dead(v1));
Insert(Dead(v0), Dead(u1));
}
for (int i = 1; i <= 2 * C; ++i) if (!dfn[i]) Tarjan(i);
for (int i = 1; i <= 2 * C; ++i) {
for (int x : G[i]) if (bel[x] != bel[i] && !vis[bel[x]])
G2[bel[i]].push_back(bel[x]), vis[bel[x]] = 1;
for (int x : G[i]) vis[bel[x]] = 0;
}
for (int i = 1; i <= N; ++i) key[bel[Dead(V[i].back())]] = i;
for (int i = 1; i <= scnt; ++i) {
if (key[i]) bit[i][key[i]] = 1;
for (int x : G2[i]) bit[i] |= bit[x];
}
int Ans = 0;
for (int i = 1; i <= N; ++i) bdfs[i] = bit[bel[Alive(V[i].back())]][key[bel[Dead(V[i].back())]]];
for (int j = 2; j <= N; ++j) if (!bdfs[j])
for (int i = 1; i < j; ++i) if (!bdfs[i])
if (!bit[bel[Alive(V[i].back())]][key[bel[Dead(V[j].back())]]]) ++Ans;
printf("%d\n", Ans);
return 0;
}
F - Games on DAG
也就是要问有多少种情况满足 \(1, 2\) 的 SG 值相等,用 \(2^M\) 减去后就是最终答案。
我们考虑一下先钦点 SG 值序列:\(\{x_1, x_2, \ldots , x_N\}\)。
则对于 \(x_i = v\) 的那些点,必须满足:
- 对于每个 \(k < v\),必须要向至少一个 \(x_i = k\) 的点 \(i\) 连一条边。
- 它们之间互相都不能连边。
- 对于 \(k > v\),它们向 \(x_i = k\) 的点的连边无所谓。
这样还是不太好分析。此时有一个关键的想法:
- 先把那些 \(x_i = 0\) 的点枚举出来。
这些点之间互不能连边,剩下的点中每个点都必须至少要向这些点连一条边,而这些点对剩下的点的连边是任意的。
以上是与这些点有关的边的状态,对于无关的边,把剩下的点的 \(x_i\) 全部减少 \(1\),正好对应仅考虑剩下的点的导出子图的情况。
于是容易写出 DP:令 \(\mathrm{f}[S]\) 表示仅考虑 \(S\)(必须保证 \(1, 2 \in S\))中的点的导出子图时,满足 \(1, 2\) 的 SG 值相等的连边方案数。
转移时我们枚举 \(T\) 为 \(S\) 的一个子集,表示 \(S \setminus T\) 为 SG 值全 \(0\) 的子集,然后从 \(\mathrm{f}[T]\) 转移。
当然,上面仅是 \(1, 2 \in T\) 的情况,对于 \(1, 2 \in S \setminus T\) 的情况,也就是 \(1, 2\) 的 SG 值均为 \(0\),则 \(T\) 中的连边就不重要了,随便连。
适当地预处理一些辅助数组,可以得到 \(\mathcal O (3^N N)\) 的时间复杂度。
#include <cstdio>
typedef long long LL;
const int Mod = 1000000007;
const int MN = 15;
int N, M, A[MN][MN];
int w[MN * MN];
int c[1 << MN][MN], f[1 << MN];
int main() {
scanf("%d%d", &N, &M), w[0] = 1;
for (int i = 1; i <= M; ++i) w[i] = 2 * w[i - 1] % Mod;
for (int i = 1, x, y; i <= M; ++i) scanf("%d%d", &x, &y), A[--x][--y] = 1;
for (int S = 1; S < 1 << N; ++S) {
int j = 0;
while (~S >> j & 1) ++j;
for (int u = 0; u < N; ++u)
c[S][u] = c[S ^ 1 << j][u] + A[u][j];
}
for (int S = 0; S < 1 << N; ++S) if ((S & 3) == 3) {
f[S] = 1;
for (int T = S & (S - 1); T; --T &= S) if ((T & 1) == (T >> 1 & 1)) {
if (T & 1) {
int Coef = 1;
for (int i = 0; i < N; ++i) if (S >> i & 1) {
if (T >> i & 1) Coef = (LL)Coef * (w[c[S ^ T][i]] - 1) % Mod;
else Coef = (LL)Coef * w[c[T][i]] % Mod;
}
f[S] = (f[S] + (LL)Coef * f[T]) % Mod;
} else {
int Coef = 1;
for (int i = 0; i < N; ++i) if (S >> i & 1) {
if (T >> i & 1) Coef = (LL)Coef * (w[c[S ^ T][i]] - 1) % Mod * w[c[T][i]] % Mod;
else Coef = (LL)Coef * w[c[T][i]] % Mod;
}
f[S] = (f[S] + Coef) % Mod;
}
}
}
printf("%d\n", (w[M] - f[(1 << N) - 1] + Mod) % Mod);
return 0;
}