A. 2090 Virus
给定 个字符串,问每个字符串是否满足下述条件:
- 字符串长度恰好为 。
- 字符串中第 个字符是辅音字母。
- 字符串中第 个字符是元音字母。
模拟。
int check(string s, int pos) { if (s[pos] == 'a' || s[pos] == 'e' || s[pos] == 'i' || s[pos] == 'o' || s[pos] == 'u') return 1; else return 0;}void solve() { string s; cin >> s; if (s.size() == 8 && (check(s, 1) && check(s, 3) && check(s, 5) && check(s, 7)) && !(check(s, 0) || check(s, 2) || check(s, 4) || check(s, 6))) cout << "Suspected Virus\n"; else cout << "Well-Being\n";}C. Fish Eating
给定一个 的网格,在网格上有一个大鱼吃小鱼的游戏,要求不经过障碍、可以吃不大于自己的鱼、每吃一条鱼大小加 。初始时所有格子都是障碍,有 个操作,操作有两种:
- 在 处放一条大小为 的鱼,问这条鱼最多吃掉多少鱼。保证每个 都不小于之前的 。
- 假设将 处的鱼放大,问最少放大多少,能使这条鱼吃掉最多的鱼。
关键在于 “保证每个 都不小于之前的 “,这样我们可以使用并查集。对于每个操作 1,显然我们可以将其相邻的所有鱼连同自己加入到一个集合。
这样可以吃到的鱼就是 。
实际上我认为难点在于处理操作 2,而 mx 数组就是为了实现这个操作。
对于鱼 ind,用 mx[ind] 表示吃掉所在集合全部鱼所需的最小 ,用 mx[ind] 减去原本的 v[ind] 与 取较大值即为答案。那要怎么维护 mx 数组呢?
首先我们要确定,每次加入新的鱼,都需要对他周围鱼的 mx 进行更新。
把新鱼吃掉所需的最小 ,即新鱼的 减去周围鱼所在集合合体能增加的重量。
而此时周围鱼的 mx 就是 。
在通过 merge 合并新鱼之后,更新原本集合的所有 mx 显然是不现实的,所以就像通过 find 传递 f 一样,我们也通过 find 传递 mx。
每次我们把当前的 mx[x] 更新为自己和父亲节点中较大的 mx,通过递归调用,即可更新当前节点到跟节点路径上所有未更新的的 mx。实际上,这类似于一种懒标记,只在需要的时候更新。
int n, m, q;vector<vector<int>> g;int dx[] = { 0, 0, -1, 1 }, dy[] = { -1, 1, 0, 0 };struct DSU { vector<int> f, sz, mx, v; DSU() { } DSU(int n) { init(n); } void init(int n) { f.resize(n); iota(f.begin(), f.end(), 0); mx.assign(n, 0); v.assign(n, 0); sz.assign(n, 1); } int find(int x) { if (f[x] == x) return x; int tmp = find(f[x]); mx[x] = max(mx[x], mx[f[x]]); return f[x] = tmp; } bool merge(int now, int nxt) { now = find(now); now = find(nxt); if (now == nxt) return false; f[nxt] = now; sz[now] += sz[nxt]; mx[nxt] = max(mx[now], v[now] - sz[nxt] + 1); return true; }};DSU dsu;signed main() { cin >> n >> m >> q; g.resize(n, vector<int>(m, -1)); dsu.init(q); int x, y, pre = 0, op, val = 0; for (int i = 0; i < q; i++) { cin >> op >> x >> y; x = x ^ pre; y = y ^ pre; x--; y--; if (op == 1) { g[x][y] = i; cin >> val; dsu.v[i] = val; for (int i = 0; i < 4; i++) { int nx = x + dx[i], ny = y + dy[i]; if (nx >= 0 && nx < n && ny >= 0 && ny < m && g[nx][ny] != -1) { dsu.merge(i, g[nx][ny]); } } pre = dsu.sz[dsu.find(i)] - 1; } else { int ind = g[x][y]; dsu.find(ind); pre = max(0LL, dsu.mx[ind] - dsu.v[ind]); } cout << pre << endl; } return 0;}E. Permutation Evaluation
对于一个 到 的排列 ,定义其权值 。给定一排列,求其权值。
对于任意位置 ,其对于答案的贡献是 。
signed main() { int n; cin >> n; vector<int> p(n), pre(n); for (int i = 0; i < n; i++) { cin >> p[i]; } pre[n - 1] = p[n - 1]; for (int i = n - 2; i >= 0; i--) { pre[i] = pre[i + 1] + p[i]; } int ans = 0; for (int i = 0; i < n - 1; i++) { ans += pre[i + 1] - (n - 1 - i) * p[i]; } cout << ans << endl; return 0;}F. Permutation Generation
对于一个 到 的排列 ,定义其权值 。给定一排列和两整数 。 求一排列 满足 且 。
注意到,对于一排列 ,循环位移任意位后 不变。
记录原本 所在位置 pos,循环位移位数即为 。
模拟循环位移即得答案。
signed main() { int n, k, x, pos = -1; cin >> n >> k >> x; vector<int> p(n); for (int i = 0; i < n; i++) { cin >> p[i]; if (p[i] == x) pos = i; } int d = (k - pos + n) % n; for (int i = 0; i < n; i++) { cout << p[(i - d + n) % n] << ' '; } return 0;}G. Precision Error?!
给定一个整数 ,需要构建一个点集 ,其中有不超过 个点位于三维欧几里得空间中。 要求对于每个点 恰好有 个不同的点 满足 ,其中 表示两点的欧几里得距离。 允许误差 ,即满足下面条件视为正确答案:
- 对于任意两个不同的点 ,有 。
- 对于每个点 ,恰好有 个点 满足 。
依旧注意到这一块。由于 的存在,假设 ,可得到 。 此时相对于 处的点,在 处恰好有长宽为 的正方形可以放得开 个满足 的点,在正方形中共可以放置 个点。满足题意要求。
const double d=0.0105;void solve(){ int n;cin>>n; cout<<2*n<<endl; for(int i=0;i<n;i++){ double x=(i%10)*d; double y=(i/10)*d; cout<<x<<' '<<y<<' '<<0.0<<endl; } for(int i=0;i<n;i++){ double x=(i%10)*d; double y=(i/10)*d; cout<<x<<' '<<y<<' '<<1.0<<endl; }}H. Rock-Paper-Scissors Master
Alice 和 Bob 玩一个 轮的石头剪刀布游戏。游戏开始时,两人各有 张牌,每张牌是 石头 (R)、剪刀 (S) 或布 (P)。每轮游戏规则如下:
- 双方可看到彼此手中的全部 张牌。
- Alice 先选一张手牌打出。
- Bob 在看到 Alice 打出的牌后,选择自己的一张牌打出。
- 根据标准规则判断胜负。若 Alice 获胜则得到 分,平局则得到 分,输得 分。
- 双方丢弃打出的牌,并独自等概率地获得一张新的石头、剪刀或布。
Alice 希望最大化自己的期望总分,Bob 则希望最小化 Alice 的期望总分。 给定游戏轮数 、Alice 和 Bob 的初始手牌,求出双方均采用最优策略时,Alice 的最终得分。
期望 DP。其实在补题的时候对我影响最大的是编码状态。总纠结于 种状态,想要找到一种最优编码,反倒浪费了很多时间,实际上直接用 3 进制也是可以的。下面给出一种 GPT 实现的 种状态的编码方式。
int encode(int r, int s, int p) {//每人 10 种状态,分别作为十位和个位。 int rank = r * 4 - r * (r - 1) / 2; rank += s; return rank;}这道题解决了编码之后的第二个点,就是要注意到,在足够多轮次之后, 每一轮的期望得分收敛为常数这个事实。 严谨的数学推导我不太擅长,但是在被告知这个事实后,略加思考便可以知道这个结论的正确性。
也就是说,大 对于我们的影响微乎其微,我们只需要计算出在不同初始状态下的小轮次中的期望得分即可。这就是 init() 在做的事。
而对于每轮游戏,我们不能想当然地认为 Bob 一定会获胜,Bob 可能当前手中没有获胜所需的牌,也可能赢了这轮,反倒导致后面全盘皆输,所以我们需要计算所有可能后的期望。
每轮游戏 Alice 和 Bob 各自可能出 种牌,可能获得 种牌,共 种可能,而我们只需要计算较小轮次。
假设计算前 轮,最优编码下,只需要计算 次。
即使设置共 种状态,也只需要计算 次。
对于每轮游戏期望得分的计算,由于我们枚举了状态,假设开始前状态为 ,结束后状态为 ,当前轮次得分为 。 这里使用开始前状态为 的原因是,对于状态 ,我们有不同的方式可以到达。 每种出牌的可能我们需要计算得分,然后再根据得到的牌来确定状态,最后确定期望得分。 而 Bob 希望期望得分最小,Alice 希望期望得分最大。所以 Bob 选择所有期望里最小的,而 Alice 选择 Bob 的选择中最大的。 那么状态转移显然为 。
在大轮次游戏中,由于每轮的期望得分收敛,完全可以用一个常数乘以轮数代替,最后得到如下代码。
map<char, int> mp = { { 'R', 0 }, { 'S', 1 }, { 'P', 2 } };double dp[101][729];int encode(vector<int> state) { int ans = 0; for (int i = 5; i >= 0; i--) { ans = ans * 3 + state[i]; } return ans;}vector<int> decode(int x) { vector<int> state(6); for (int i = 0; i < 6; i++) { state[i] = x % 3; x /= 3; } return state;}double win(int a, int b) { if ((a == 0 && b == 1) || (a == 1 && b == 2) || (a == 2 && b == 0)) return 3; else if (a == b) return 1; else return 0;}void init() { for (int i = 1; i < 101; i++) { for (int j = 0; j < 729; j++) { vector<int> state = decode(j); double mx = 0; for (int a = 0; a < 3; a++) { double mn = 1e18; for (int b = 3; b < 6; b++) { double sum = win(state[a], state[b]); for (int nxt1 = 0; nxt1 < 3; nxt1++) { int tmp1 = state[a]; state[a] = nxt1; for (int nxt2 = 0; nxt2 < 3; nxt2++) { int tmp2 = state[b]; state[b] = nxt2; sum += dp[i - 1][encode(state)] / 9; state[b] = tmp2; } state[a] = tmp1; } mn = min(mn, sum); } mx = max(mx, mn); } dp[i][j] = mx; } }}void solve() { int k; cin >> k; string alice, bob; cin >> alice >> bob; vector<int> state(6); for (int i = 0; i < 3; i++) { state[i] = mp[alice[i]]; state[i + 3] = mp[bob[i]]; } if (k <= 100) cout << dp[k][encode(state)] << endl; else { double avg = (dp[100][0] - dp[99][0]); cout << dp[100][encode(state)] + avg * (k - 100) << endl; }}signed main() { cout << fixed << setprecision(12); init(); int _t; cin >> _t; while (_t--) solve(); return 0;}J. Show Hand
模拟德州扑克。。没啥可说的,今年我见过的最极致最纯粹的模拟。
L. Substrings of Substrings
AC 自动机 · 改。等有空再补题解吧,我要先补题了。。。
using namespace std;const int MOD = 998244353;int n, q;string S;vector<int> a, pre, minpre, maxsuff, prepre, presuff;vector<int> length, lastpos, finalans, finalsum;inline int mmod(int x) { x %= MOD; return (x < 0 ? (x + MOD) : x);}void solve(int id, int l) { int len = length[id]; int r = l + len - 1; int mx = maxsuff[r + 1] - minpre[l]; finalans[id] = max(finalans[id], mx); int previous = lastpos[id]; int countl = l - previous, countr = n - r; int lsum = prepre[l]; if (previous >= 0) lsum = mmod(lsum - prepre[previous]); int rsum = presuff[r + 1]; finalsum[id] = mmod(finalsum[id] + mmod(mmod(countl) * rsum - mmod(countr) * lsum)); lastpos[id] = l;}struct AC { struct Node { int child[26], fail, outputlink; vector<int> index; Node() { memset(child, -1, sizeof(child)); fail = 0; outputlink = 0; } }; vector<Node> tree; vector<int> length; AC(int q) { tree.emplace_back(); length.resize(q); } void insert(string& s, int id) { int u = 0; length[id] = s.size(); for (char x : s) { int c = x - 'a'; if (tree[u].child[c] == -1) { tree[u].child[c] = tree.size(); tree.emplace_back(); } u = tree[u].child[c]; } tree[u].index.push_back(id); } void build() { queue<int> q; for (int c = 0; c < 26; c++) { int v = tree[0].child[c]; if (v == -1) tree[0].child[c] = 0; else { tree[v].fail = 0; tree[v].outputlink = 0; q.push(v); } } while (q.size()) { int u = q.front(); q.pop(); for (int c = 0; c < 26; c++) { int v = tree[u].child[c]; if (v == -1) { tree[u].child[c] = tree[tree[u].fail].child[c]; continue; } tree[v].fail = tree[tree[u].fail].child[c]; int f = tree[v].fail; if (tree[f].index.size()) tree[v].outputlink = f; else tree[v].outputlink = tree[f].outputlink; q.push(v); } } } void query(const string& s) { int u = 0; for (int r = 0; r < s.size(); r++) { int c = s[r] - 'a'; u = tree[u].child[c]; for (int id : tree[u].index) { int l = r - length[id] + 1; solve(id, l); } for (int v = tree[u].outputlink; v != 0; v = tree[v].outputlink) { for (int id : tree[v].index) { int l = r - length[id] + 1; solve(id, l); } } } }};
signed main() { std::ios::sync_with_stdio(false); std::cin.tie(0); cin >> n >> q; cin >> S; a.resize(n), pre.resize(n + 1); minpre.resize(n + 1), maxsuff.resize(n + 1); prepre.resize(n + 1), presuff.resize(n + 1); for (int i = 0; i < n; i++) cin >> a[i]; for (int i = 1; i <= n; i++) pre[i] = pre[i - 1] + a[i - 1]; for (int i = 1; i <= n; i++) minpre[i] = min(minpre[i - 1], pre[i]); maxsuff[n] = pre[n]; for (int i = n - 1; i >= 0; i--) maxsuff[i] = max(maxsuff[i + 1], pre[i]); for (int i = 1; i <= n; i++) prepre[i] = mmod(prepre[i - 1] + pre[i]); presuff[n] = mmod(pre[n]); for (int i = n - 1; i >= 0; i--) presuff[i] = mmod(presuff[i + 1] + pre[i]); AC ac(q); unordered_map<string, int> mp; vector<int> rmp(q); int uniquecount = 0; for (int i = 0; i < q; i++) { string p; cin >> p; auto it = mp.find(p); if (it == mp.end()) { mp[p] = uniquecount; rmp[i] = uniquecount; ac.insert(p, uniquecount); uniquecount++; } else rmp[i] = it->second; } length = ac.length; lastpos.resize(uniquecount, -1); finalans.resize(uniquecount, LLONG_MIN); finalsum.resize(uniquecount, 0); ac.build(); ac.query(S); for (int i = 0; i < q; i++) { int id = rmp[i]; cout << finalans[id] << ' ' << finalsum[id] << endl; } return 0;}部分信息可能已经过时
