2017-2018 ACM-ICPC Southeastern European Regional Programming Contest (SEERC 2017)
2017-2018 ACM-ICPC Southeastern European Regional Programming Contest (SEERC 2017)
全靠 wxh的博客 补完这套。wxhtxdy!
$f[i][k]$ 表示在第 $i$ 个位置刚好匹配了 $k$ 个字符。转移方程 $$ f[i][k] = \sum_{i - j > h[s[k - 1]]} f[j][k - 1] $$
前缀和优化加滚动就行了。
好像可以直接用 $f[i][k]$ 表示前缀和,就没这么多事了。

#include <bits/stdc++.h> const int N = 1e5 + 7; const int MOD = 1e9 + 7; int dp[2][N], sum[2][N], h[N]; char s[N], t[N]; void M(int &a) { if (a >= MOD) a -= MOD; } int main() { freopen("in.txt", "r", stdin); int k, n; scanf("%d%d", &k, &n); for (int i = 0; i < 26; i++) scanf("%d", h + i); scanf("%s%s", t + 1, s + 1); for (int i = 1; i <= n; i++) { if (s[i] == t[1]) dp[1][i] = 1; sum[1][i] = sum[1][i - 1] + dp[1][i]; } for (int j = 2; j <= k; j++) { int cur = j & 1, pre = cur ^ 1; memset(dp[cur], 0, sizeof(dp[cur])); memset(sum[cur], 0, sizeof(sum[cur])); for (int i = 1; i <= n; i++) { if (s[i] == t[j]) { int where = i - h[t[j - 1] - 'A'] - 1; if (where > 0) M(dp[cur][i] += sum[pre][where]); } //if (i == 2) printf("%d\n", dp[cur][i]); M(sum[cur][i] += sum[cur][i - 1]); M(sum[cur][i] += dp[cur][i]); } } printf("%d\n", sum[k & 1][n]); return 0; }
操作顺序对答案无影响,从前往后DP即可。
枚举空位进行DP,$f[i]$ 表示 $i$ 之前已经放好,并且产生了 $i - 1 - cnt[i - 1]$ 个空位的方案数, $g[i]$ 则表示产生了 $i$ 个空位的方案数。

#include <bits/stdc++.h> const int N = 1e6 + 7; const int MOD = 1e9 + 7; int f[N], g[N], cnt[N]; void M(int &a) { if (a >= MOD) a -= MOD; } int main() { int n, m; scanf("%d%d", &n, &m); n++; while (m--) { int x; scanf("%d", &x); cnt[x]++; } for (int i = 1; i <= n; i++) cnt[i] += cnt[i - 1]; g[0] = 1; for (int i = 1; i <= n; i++) { if (cnt[i] == cnt[i - 1]) { if (i - 1 - cnt[i - 1] >= 0) f[i] = g[i - 1 - cnt[i - 1]]; if (i - cnt[i] >= 0) M(g[i - cnt[i]] += f[i]); } } printf("%d\n", f[n]); return 0; }
对于每种颜色能求出其极长链,也就是每次操作两端的端点。
然后操作之间相当于一个拓扑序,对每一种颜色向其链上不同颜色的点连边,拓扑排序即可。
连边需要用倍增优化,就是一次连边只需要连 $logn$ 条边。

#include <bits/stdc++.h> #define pii pair<int, int> #define fi first #define se second const int N = 1e5 + 7; const int M = 17; const int S = N * 20; int n, m, color[N], dep[N], degree[S], fa[N][M], id[N][M]; std::vector<int> vec[N], col[N], to[S]; void dfs(int u, int pre = 0) { dep[u] = dep[pre] + 1; fa[u][0] = pre; for (int i = 1; i < M; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1]; for (int v: vec[u]) if (v != pre) dfs(v, u); } int jump(int u, int d) { for (int i = 0; i < M; i++) if (d >> i & 1) u = fa[u][i]; return u; } int Lca(int u, int v) { if (dep[u] < dep[v]) std::swap(u, v); u = jump(u, dep[u] - dep[v]); if (u == v) return u; for (int i = M - 1; ~i; i--) if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i]; return fa[u][0]; } int dist(int u, int v) { return dep[u] + dep[v] - 2 * dep[Lca(u, v)]; } int find(int u, int v, int z, int d) { if (d <= dep[u] - dep[z]) return jump(u, d); return jump(v, dep[u] + dep[v] - 2 * dep[z] - d); } void addedge(int color, int u, int v) { if (dep[u] < dep[v]) std::swap(u, v); int dif = dep[u] - dep[v]; for (int i = 0; i < M; i++) if (dif >> i & 1) { to[color].push_back(id[u][i]); u = fa[u][i]; } if (u == v) { to[color].push_back(id[u][0]); return; } for (int i = M - 1; ~i; i--) if (fa[u][i] != fa[v][i]) { to[color].push_back(id[u][i]); to[color].push_back(id[v][i]); u = fa[u][i]; v = fa[v][i]; } to[color].push_back(id[u][0]); to[color].push_back(id[v][0]); to[color].push_back(id[fa[u][0]][0]); } int X[N], Y[N]; int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { scanf("%d", color + i); col[color[i]].push_back(i); } for (int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); vec[u].push_back(v); vec[v].push_back(u); } dfs(1); int tol = m; for (int i = 0; i < M; i++) for (int j = 1; j <= n; j++) id[j][i] = ++tol; for (int i = 1; i <= n; i++) to[id[i][0]].push_back(color[i]); for (int i = 1; i < M; i++) for (int j = 1; j <= n; j++) if (fa[j][i - 1]) { to[id[j][i]].push_back(id[j][i - 1]); to[id[j][i]].push_back(id[fa[j][i - 1]][i - 1]); } for (int i = 1; i <= m; i++) { if (col[i].empty()) { printf("%d 1 1\n", i); continue; } int x = col[i][0], y = col[i][0], d = 0; for (int j = 1; j < col[i].size(); j++) { int z = col[i][j], dd; dd = dist(x, z); int flag = 0; if (dd > d) { d = dd; flag = 1; } dd = dist(y, z); if (dd > d) { d = dd; flag = 2; } if (flag == 1) { y = z; } else if (flag == 2) { x = z; } } X[i] = x; Y[i] = y; int z = Lca(x, y); std::vector<std::pii> all; for (int p: col[i]) all.push_back(std::pii(dist(x, p), p)); std::sort(all.begin(), all.end()); for (int j = 1; j < all.size(); j++) if (all[j].fi != all[j - 1].fi + 1) { int u = find(x, y, z, all[j].fi - 1), v = find(x, y, z, all[j - 1].fi + 1); addedge(i, u, v); } } for (int i = 1; i <= tol; i++) for (auto v: to[i]) degree[v]++; std::queue<int> que; for (int i = 1; i <= tol; i++) if (!degree[i]) que.push(i); while (!que.empty()) { int u = que.front(); que.pop(); if (u <= m && !col[u].empty()) printf("%d %d %d\n", u, X[u], Y[u]); for (int v: to[u]) if (!--degree[v]) que.push(v); } return 0; }
[D - Harry Potter and The Vector Spell]
并查集合并每一列中两个 $1$,不在同一个集合里则对答案贡献为 $1$

#include <bits/stdc++.h> const int N = 1e5 + 7; int fa[N]; int getfa(int x) { return x == fa[x] ? x : fa[x] = getfa(fa[x]); } std::vector<int> G[N]; int main() { int n, m; scanf("%d%d", &m, &n); for (int i = 1; i <= m; i++) { fa[i] = i; int k; scanf("%d", &k); while (k--) { int u; scanf("%d", &u); G[u].push_back(i); } } int ans = 0; for (int i = 1; i <= n; i++) { if (G[i].size() == 0) continue; if (G[i].size() == 1) { ans++; continue; } int u = G[i][0], v = G[i][1]; u = getfa(u); v = getfa(v); if (u != v) { ans++; fa[u] = v; } } printf("%d\n", ans); }
预处理下每个 major scale 以及其子集。
然后枚举开头和结尾拼起来的是哪个 major scale,中间段再贪心即可。

#include <bits/stdc++.h> std::map<std::string, int> mp; const int N = 1e7 + 7; int a[N]; bool flag[N]; const int offset[8] = {0, 2, 4, 5, 7, 9, 11, 12}; void print(int x) { for (int i = 13; ~i; i--) printf("%d", x >> i & 1); puts(""); } int main() { std::ios::sync_with_stdio(false); std::cin.tie(0); mp["Do"] = 0; mp["Do#"] = 1; mp["Re"] = 2; mp["Re#"] = 3; mp["Mi"] = 4; mp["Fa"] = 5; mp["Fa#"] = 6; mp["Sol"] = 7; mp["Sol#"] = 8; mp["La"] = 9; mp["La#"] = 10; mp["Si"] = 11; for (int i = 0; i < 12; i++) { int s = 0; for (int j = 0; j < 8; j++) s |= 1 << ((i + offset[j]) % 12); flag[s] = 1; } for (int i = 1; i < 1 << 12; i++) for (int j = i; j; j = (j - 1) & i) flag[j] |= flag[i]; int n; std::cin >> n; for (int i = 1; i <= n; i++) { std::string str; std::cin >> str; a[i] = mp[str]; } assert(!flag[(1 << 12) - 1]); int ans = n; for (int i = 0; i < 12; i++) { int s = 0; for (int j = 0; j < 8; j++) s |= 1 << ((i + offset[j]) % 12); int l = 1, r = n; while (l <= n && (s >> a[l] & 1)) l++; while (r && (s >> a[r] & 1)) r--; int cur = 1; s = (1 << 12) - 1; for (int j = l; j <= r; j++) { if (!flag[s | (1 << a[j])]) { cur++; s = 1 << a[j]; } else { s |= 1 << a[j]; } } ans = std::min(ans, cur); } std::cout << ans << '\n'; return 0; }
贪心地考虑,先把不匹配的 $1$ 从大到小消掉,再把不匹配的 $0$ 从小到大加上。但是如果存在一些匹配了的 $1$ 它们的花费特别大,就不是最优的。
那么就枚举多消去几个已经匹配了的 $1$,这部分肯定也是从大到小优。
用multiset维护即可。

#include <bits/stdc++.h> #define ll long long const int N = 1e4 + 7; std::multiset<ll, std::greater<ll> > st1; std::multiset<ll> st2; std::vector<ll> vec; int n; ll c[N]; ll sum; char a[N], b[N]; ll solve(int cnt) { ll ans = 0; ll s = sum; if (cnt) st1.insert(vec[cnt - 1]), st2.insert(vec[cnt - 1]); for (auto it: st1) { s -= it; ans += s; } for (auto it: st2) { s += it; ans += s; } return ans; } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%lld", c + i); scanf("%s", a + 1); scanf("%s", b + 1); for (int i = 1; i <= n; i++) { if (a[i] != b[i]) { if (a[i] == '1') st1.insert(c[i]); else st2.insert(c[i]); } else { if (a[i] == '1') vec.push_back(c[i]); } if (a[i] == '1') sum += c[i]; } std::sort(vec.begin(), vec.end(), std::greater<ll>()); ll ans = 1e18; for (int i = 0; i <= vec.size(); i++) ans = std::min(ans, solve(i)); printf("%lld\n", ans); return 0; }

#include <bits/stdc++.h> #define ll long long const int N = 1e4 + 7; struct In { ll a, t; bool operator < (const In &rhs) const { return a > rhs.a; } } in[N]; int main() { int n; scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%lld%lld", &in[i].a, &in[i].t); ll v = 0; double ans1 = 0; for (int i = 0; i < n; i++) { ans1 += v * in[i].t + 0.5 * in[i].a * in[i].t * in[i].t; v += in[i].a * in[i].t; } std::sort(in, in + n); v = 0; double ans = 0; for (int i = 0; i < n; i++) { ans += v * in[i].t + 0.5 * in[i].a * in[i].t * in[i].t; v += in[i].a * in[i].t; } printf("%.1f\n", ans - ans1); return 0; }
如果老鼠和猫现在在一条边的两个端点 $(u, v)$ 上,老鼠先走一步到 $w$。
1. 如果老鼠走第二步不回到 $u$,那么猫肯定得跟过去。
2. 如果老鼠第二步走回 $u$,那么猫可以先不走,让老鼠回到 $u$,它再走到 $u$,老鼠走一步到 $w$,猫再跟上,老鼠走一步到 $u$,猫不动,这样就是猫用了 $4$ 步把老鼠在 $u$ 的后继节点 $w$ 给堵住了。
这样的话对每一条边 $(u, v)$ 设为 $(mouse, cat)$。预处理出后继状态以及到终态的最短路。
然后枚举一下老鼠走了几步到达和猫相邻的状态,通过节点深度判断猫是否能到这个状态,可以的话更新答案。

#include <bits/stdc++.h> const int N = 1e5 + 7; const int M = N * 4; const int INF = 0x3f3f3f3f; #define pii pair<int, int> #define fi first #define se second std::vector<std::pii> edge, to[M]; std::vector<int> vec[N], temp[M]; int dis[N], dep[N]; int n, mouse; void dfs(int u, int fa) { for (int v: vec[u]) { if (v == fa) continue; dep[v] = dep[u] + 1; dfs(v, u); } } int getid(int x, int y) { return lower_bound(edge.begin(), edge.end(), std::pii(x, y)) - edge.begin(); } int getnext(int u, int no) { for (int v: vec[u]) if (v != no) return v; return -1; } int main() { int T; scanf("%d", &T); while (T--) { scanf("%d%d", &n, &mouse); mouse--; for (int i = 0; i < n; i++) vec[i].clear(), dep[i] = 0; edge.clear(); for (int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); u--; v--; vec[u].push_back(v); vec[v].push_back(u); edge.push_back(std::pii(u, v)); edge.push_back(std::pii(v, u)); } std::sort(edge.begin(), edge.end()); for (int i = 0; i < n; i++) std::reverse(vec[i].begin(), vec[i].end()); if (!~getnext(mouse, -1)) { puts("0"); continue; } dfs(0, -1); for (int i = 0; i < n * 4; i++) temp[i].clear(); for (int i = 0; i < edge.size(); i++) dis[i] = INF, to[i].clear(); for (int i = 0; i < edge.size(); i++) { int u = edge[i].fi, v = edge[i].se; int w = getnext(u, v); if (!~w) { dis[i] = 0; temp[0].push_back(i); } else { to[getid(w, u)].push_back(std::pii(i, 1)); if (getnext(w, -1) == u) to[getid(u, w)].push_back(std::pii(i, 4)); } } for (int i = 0; i < n * 4; i++) for (int j: temp[i]) if (dis[j] == i) for (auto p: to[j]) if (dis[p.fi] > i + p.se) { dis[p.fi] = i + p.se; temp[dis[p.fi]].push_back(p.fi); } int ans = INF, cur = mouse; for (int i = 0; i < n; i++) { int ne = getnext(cur, i ? -1 : 0); if (dep[ne] < i || (dep[ne] == i && dep[cur] != i - 1)) ans = std::min(ans, i + dis[getid(cur, ne)]); cur = ne; } printf("%d\n", ans); } return 0; }
虽说总的状态数是 1 << 30 个,但真正的状态数不会很多,很多状态都无法达到。
所以直接记忆化搜索(抄了wxh的代码,对于位运算部分的处理tql)

#include <bits/stdc++.h> const int N = 55; const char s[3][N] = { "010111110011110010010010011001", "111100011010010010111011111111", "010100001110011111011110110110" }; std::unordered_map<int, int> mem[N], vis[N]; int n, a[N]; int go(int state, int type, int rotate) { if (state >> 21) return -1; char b[3][3]; if (!rotate) { for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) b[i][j] = s[i][j + type * 3]; } else if (rotate == 1) { for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) b[i][j] = s[j][2 - i + type * 3]; } else if (rotate == 2) { for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) b[i][j] = s[2 - i][2 - j + type * 3]; } else { for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) b[i][j] = s[2 - j][i + type * 3]; } int p = 7; for (int i = 6; ~i; --i) { bool flag = false; for (int j = 0; j < 3; ++j) for (int k = 0; k < 3; ++k) if (b[j][k] == '1' && (state >> (i + j) * 3 + k & 1)) flag = true; if (flag) break; p = i; } int new_state = state; for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) if (b[i][j] == '1') new_state |= 1 << (i + p) * 3 + j; for (int i = 0; i < 10; ++i) while ((new_state >> i * 3 & 7) == 7) new_state = (new_state & (1 << 3 * i) - 1) | (new_state >> (i + 1) * 3 << i * 3); return new_state; } int dfs(int state, int cur) { if (vis[cur][state] == 1) return -1; if (vis[cur][state] == 2) return mem[cur][state]; vis[cur][state] = 1; int ans = 0; for (int i = 0; i < 4; i++) { int ne = go(state, a[cur], i); if (~ne) { int val = dfs(ne, (cur + 1) % n); if (!~val) return -1; ans = std::max(ans, val + 1); } } vis[cur][state] = 2; mem[cur][state] = ans; return ans; } int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", a + i); printf("%d\n", dfs(0, 0)); return 0; }
三个人的nim...
如果全是 $1$,那么 $n \equiv 0$ (mod $3$)时必输,否则必胜。
如果只有一个不是 $1$,如果 $n \equiv 0$ (mod $3$),第一个人把 非 $1$ 那堆取出 $1$ 就必胜,否则把那一堆取光。
如果有两个不是 $1$,且至少有一个 $2$ 并且 $1$ 的个数不是 $3$ 的倍数,第一个人都可以把一堆取光或者取剩下 $1$ 使得自己赢,否则必败,另外两个人都可以让 $n \equiv 0$ (mod $3$)
如果大于两个,另外两个人的操作空间比第一个人大,就可以让第一个人必败了。

#include <bits/stdc++.h> int main() { int n; int cnt1 = 0, cnt2 = 0; scanf("%d", &n); for (int i = 1; i <= n; i++) { int x; scanf("%d", &x); if (x == 1) cnt1++; else if (x == 2) cnt2++; } if (cnt1 == n) puts(n % 3 == 0 ? "Lose" : "Win"); else if (cnt1 == n - 1) puts("Win"); else if (cnt1 == n - 2) puts((cnt2 && cnt1 % 3 != 0) ? "Win" : "Lose"); else puts("Lose"); }
对于 $n$ 它的最长上升子序列肯定是 $1$,对于 $n-1$,如果它在 $n$ 后面那么肯定是 $1$,如果在 $n$ 前面那么肯定是 $2$,但是放后面会使得字典序更小。
那么就按最长上升子序列的值排个序。值小的放大的数,同权值的,越靠前面的放越大的数才符合最长上升子序列的值。

#include <bits/stdc++.h> const int N = 1e5 + 7; struct In { int pos, val; bool operator < (const In &rhs) const { if (val == rhs.val) return pos < rhs.pos; return val < rhs.val; } } in[N]; int ans[N]; int main() { int n; scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d", &in[i].val); in[i].pos = i; } std::sort(in + 1, in + 1 + n); for (int i = 1; i <= n; i++) ans[in[i].pos] = n - i + 1; for (int i = 1; i <= n; i++) printf("%d%c", ans[i], " \n"[i == n]); return 0; }
总边数是 $4(n-1)$,至少存在一个点的度数不超过 $3$,那么答案肯定不超过 $3$,并且有一棵树的只割去一条边。
枚举 $A$ 树中每一条边作为割边,在 $B$ 树中这条边连接的两个端点在 $A$ 树中不在一个连通块中,那么这条边需要割掉。
树上启发式合并解决。

#include <bits/stdc++.h> #define pii pair<int, int> const int N = 1e5 + 7; struct Tree { std::vector<int> G[N]; int son[N], sz[N]; inline void add(int u, int v) { G[u].push_back(v); G[v].push_back(u); } void dfs(int u, int fa) { sz[u] = 1; for (auto v: G[u]) { if (v == fa) continue; dfs(v, u); sz[u] += sz[v]; if (sz[son[u]] < sz[v]) son[u] = v; } } } a, b; bool skip[N], color[N]; int cur, n; void cal(int u, const Tree &a) { color[u] ^= 1; for (auto v: a.G[u]) if (color[v] != color[u]) cur++; else cur--; } void edt(int u, int fa, const Tree &a, const Tree &b) { cal(u, b); for (auto v: a.G[u]) if (v != fa && !skip[v]) edt(v, u, a, b); } void dfs(int u, int fa, bool keep, const Tree &a, const Tree &b, std::pii &ans) { for (auto v: a.G[u]) if (v != fa && v != a.son[u]) dfs(v, u, 0, a, b, ans); if (a.son[u]) dfs(a.son[u], u, 1, a, b, ans), skip[a.son[u]] = 1; edt(u, fa, a, b); if (fa) { if (cur < ans.first) ans = std::pii(cur, 0); if (ans.first == cur) ans.second++; } if (a.son[u]) skip[a.son[u]] = 0; if (!keep) edt(u, fa, a, b); } std::pii solve(Tree &a, Tree &b) { a.dfs(1, 0); std::pii ans = std::pii(n, 0); dfs(1, 0, 0, a, b, ans); ans.first++; return ans; } int main() { scanf("%d", &n); for (int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); a.add(u, v); } for (int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); b.add(u, v); } auto ans = solve(a, b); if (ans.first == 2) return 0 * printf("%d %d\n", 2, ans.second); cur = 0; auto res = solve(b, a); cur = 0; if (ans.first == 3) cur += ans.second; if (res.first == 3) cur += res.second; printf("%d %d\n", 3, cur); return 0; }