題目傳送門: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;
}