T1
自闭
题目条件可以扩展到任意矩形的四个顶点。则整个矩阵仅由第一行和第一列决定。容易发现最左上角的格子直接填 \(0\) 是一定合法的,因此只需要判断是否存在数组 \(a_i, b_i\) 满足 \(A_{i, j} = a_i + b_j\) 即可。考虑将给出的限制视为边,\(a_i, b_j\) 视为点建图,显然不同连通块之间没有任何关系。对于每个连通块,只需要任选一个点令其值为 \(0\),然后求出这个连通块中所有点的取值,然后只需要这个连通块中的 \(\min a_i + \min b_j \ge 0\),这个连通块就合法。当且仅当所有连通块都合法,原图合法。使用 bfs,复杂度线性。
代码
#include <iostream>
#include <string.h>
#include <queue>
using namespace std;
int n, m, K;
int head[200005], nxt[400005], to[400005], ew[400005], ecnt;
void add(int u, int v, int ww) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt, ew[ecnt] = ww; }
int dist[200005];
bool vis[200005];
queue<int> q;
int main() {
freopen("zibi.in", "r", stdin);
freopen("zibi.out", "w", stdout);
cin >> n >> m >> K;
for (int i = 1; i <= K; i++) {
int x, y, w;
cin >> x >> y >> w;
add(x, y + n, w);
add(y + n, x, w);
}
for (int i = 1; i <= n; i++) {
if (vis[i])
continue;
q.push(i);
vis[i] = 1;
int amn = 0, bmn = 2147483647;
while (!q.empty()) {
int x = q.front();
q.pop();
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if (!vis[v]) {
vis[v] = 1;
dist[v] = ew[i] - dist[x];
q.push(v);
if (v <= n)
amn = min(amn, dist[v]);
else
bmn = min(bmn, dist[v]);
} else if (dist[v] != ew[i] - dist[x]) {
cout << "Zikai\n";
return 0;
}
}
if (bmn + amn < 0) {
cout << "Zikai\n";
return 0;
}
}
}
cout << "Zibi\n";
return 0;
}
T2
狗
先二分答案,然后考虑 \(dp_i\) 表示用前 \(i\) 条狗最多能覆盖到哪一根骨头。这个 dp 和那个 Lanterns 很像,就是当前狗向前,或当前狗向后,或者当前狗向前且前一条狗向后。分别判断转移条件进行转移即可。
代码
#include <iostream>
#include <string.h>
using namespace std;
int n, dogcount, bone;
string str;
int lb[1000005], rb[1000005];
int ld[1000005], rd[1000005];
int id[1000005];
int dp[1000005], pmx[1000005];
bool chk(int k) {
int mx = 0;
memset(dp, 0, sizeof dp);
memset(pmx, 0, sizeof pmx);
for (int i = 1; i <= n; i++) {
if (str[i] != 'D')
continue;
if (mx >= lb[i])
dp[i] = max(dp[i], lb[min(n, i + k)]);
if (i <= k + 1 || dp[ld[i - 1]] >= lb[i - k - 1])
dp[i] = max(dp[i], lb[i]);
if (i >= k + 1) {
if (ld[i - 1] && ld[i - 1] >= i - k) {
if (ld[i - 1] == rd[i - k]) {
if (dp[ld[i - k - 1]] >= lb[i - k - 1])
dp[i] = max(dp[i], max(dp[ld[i - 1]], lb[min(n, ld[i - 1] + k)]));
} else {
if (dp[rd[i - k]] >= lb[i - k - 1])
dp[i] = max(dp[i], max(dp[ld[i - 1]], lb[min(n, ld[i - 1] + k)]));
}
}
} else if (ld[i - 1])
dp[i] = max(dp[i], lb[min(n, ld[i - 1] + k)]);
pmx[i] = max(dp[i], pmx[ld[i - 1]]);
mx = max(mx, dp[i]);
}
return pmx[ld[n]] >= lb[n];
}
int main() {
freopen("dogs.in", "r", stdin);
freopen("dogs.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> str;
str = ' ' + str;
for (int i = 1; i <= n + 1; i++) lb[i] = (str[i] == 'B' ? i : lb[i - 1]), ld[i] = (str[i] == 'D' ? i : ld[i - 1]), id[i] = id[i - 1] + (str[i] == 'B');
rd[n + 1] = rb[n + 1] = n + 1;
for (int i = n; ~i; i--) rd[i] = (str[i] == 'D' ? (++dogcount, i) : rd[i + 1]), rb[i] = (str[i] == 'B' ? (++bone, i) : rb[i + 1]);
if (dogcount == 1) {
int pos = 0;
for (int i = 1; i <= n; i++) {
if (str[i] == 'D')
pos = i;
}
int a = 0, b = pos, c = 0, d = pos;
for (int i = pos; i; i--) str[i] == 'B' ? (++a, b = i) : 0;
for (int i = pos; i <= n; i++) str[i] == 'B' ? (++c, d = i) : 0;
if (a > c)
cout << a << " " << pos - b << "\n";
else if (a < c)
cout << c << " " << d - pos << "\n";
else
cout << a << " " << min(pos - b, d - pos) << "\n";
return 0;
}
int l = 1, r = n, mid, ans = -1;
while (l <= r) {
mid = (l + r) >> 1;
if (chk(mid))
ans = mid, r = mid - 1;
else
l = mid + 1;
}
cout << bone << " " << ans << "\n";
return 0;
}
T3
字符串距离
瞎搜,加点剪枝就行了。退火也行。为了提高爆搜正确性,可以考虑对每一个位置先填这一位上的众数,然后按照出现次数枚举填其他字符,再按照众数出现次数从大往小填每一位。这样搜加卡时即可通过。
代码
#include <iostream>
#include <algorithm>
#include <time.h>
using namespace std;
int n, m, d, mx;
string str[1005];
string cur = " ";
int dist[1005];
int cnt[20005][30];
int p[20005][30], q[20005];
void dfs(int y) {
if (1.0 * clock() / CLOCKS_PER_SEC >= 0.95) {
cout << "-1\n";
exit(0);
}
if (y == m + 1) {
cout << cur.substr(1) << "\n";
exit(0);
}
int x = q[y];
// cout << y << " " << x << " a\n";
for (int _ = 0; _ < 26; _++) {
if (cnt[x][p[x][_]] == n)
continue;
int i = p[x][_];
// cout << _ << " " << i << " b\n";
bool ok = 1;
for (int j = 1; j <= n; j++) {
if (dist[j] + (i != str[j][x] - 'a') > d) {
ok = 0;
break;
}
}
if (ok) {
for (int j = 1; j <= n; j++) dist[j] += (i != str[j][x] - 'a');
cur[x] = i + 'a';
dfs(y + 1);
for (int j = 1; j <= n; j++) dist[j] -= (i != str[j][x] - 'a');
}
}
}
int main() {
freopen("string.in", "r", stdin);
freopen("string.out", "w", stdout);
cin >> n >> m >> d;
for (int i = 1; i <= m; i++) {
cur += ' ', q[i] = i;
for (int j = 0; j < 26; j++) cnt[i][j] = n;
}
for (int i = 1; i <= n; i++) {
cin >> str[i];
str[i] = ' ' + str[i];
for (int j = 1; j <= m; j++) --cnt[j][str[i][j] - 'a'];
}
for (int i = 1; i <= m; i++) {
for (int j = 0; j < 26; j++) p[i][j] = j;
sort(p[i], p[i] + 26, [i](int a, int b) { return cnt[i][a] < cnt[i][b]; });
}
sort(q + 1, q + m + 1, [](int a, int b) { return cnt[a][p[a][0]] < cnt[b][p[b][0]]; });
dfs(1);
cout << "-1\n";
return 0;
}
T4
独立集
莫名其妙的构造题。先把所有边从小的指向大的,只需要对每个点 \(x\) 找出一个集合 \(S(x)\),使得 存在一条从没有入度的点走到 \(x\) 的路径的长度模 \(k\) 余 \(i \iff i \in S(x)\),然后找到一个 \(c\) 使得 \(c \in S(x) \land (c \bmod k + 1) \notin S(x)\),然后把 \(x\) 染成颜色 \(c \bmod k + 1\) 即可。可以发现这是对的。
代码
#include <iostream>
#include <vector>
using namespace std;
int n, m, k;
int clr[1000005], cnt[1000005];
vector<int> vec[1000005];
int main() {
freopen("set.in", "r", stdin);
freopen("set.out", "w", stdout);
cin >> n;
cin >> n >> m >> k;
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
vec[max(u, v)].emplace_back(min(u, v));
}
for (int i = 1; i <= n; i++) {
if (vec[i].empty())
clr[i] = 1;
else {
for (int v : vec[i]) cnt[clr[v]]++;
for (int v : vec[i]) {
if (!cnt[clr[v] % k + 1]) {
clr[i] = clr[v] % k + 1;
break;
}
}
for (int v : vec[i]) cnt[clr[v]]--;
}
}
for (int i = 1; i <= n; i++) cout << clr[i] << " ";
cout << "\n";
return 0;
}