abc209

发布时间 2023-09-26 15:49:28作者: Bellala

C - Not Equal 285

求长度为 n,两两不同,且满足 \(1\le a_i\le c_i\) 的数组的数量

数组 c 排序,答案就是 \(\prod\limits_i(c_i-(i-1))\),其中 \(i-1\) 个位置被前面占了

D - Collision 686

给定一棵树,q 次询问,一个人从 u 走向 v,另一个人从 v 走向 u,同时同速。问两人在点上还是在边上相遇

若 u 和 v 深度奇偶性相同则在点上相遇,否则在边上

E - Shiritori 2153

两个人成语接龙,每个人说的单词前三个字母须与上一个人说的最后三个字母同。给定单词表,单词可重复使用,不能说者输,问从每个单词出发谁会赢(或者平局)

把每个单词视为边,把边(单词)的状态放在它的终端点(三个字母)上,按拓扑排序的逆序递推状态,最后没有状态的点是平局

(似乎我对输赢的定义跟大家相反)

先手必败点:存在先手必胜后继点

先手必胜点:所有后继点必败

注意:跟正常拓扑排序不同,不是度为0才入队,而是状态确定就入队,度只是为状态的推断服务的。否则,若 u 的后继既有必胜点又有个环,可能导致 u 的状态没法更新,但实际上 u 是必败点

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    
    int n;
    cin >> n;
    vector<string> edges;
    map<string, vector<string>> g;
    map<string, int> deg;
    while (n--) {
        string s;
        cin >> s;
        string u = s.substr(0, 3), v = s.substr(s.size() - 3); // u -> v
        edges.push_back(v);
        g[v].push_back(u); // 反图
        deg[u]++;
        if (!deg.count(v)) deg[v] = 0; // 别漏了
    }
    
    queue<string> q;
    map<string, int> f;
    for (auto [s, d] : deg) {
        if (!d) q.push(s), f[s] = 1; // 先手胜
    }
    while (q.size()) {
        auto v = q.front(); q.pop();
        for (auto u : g[v]) {
            deg[u]--;
            if (f[v] == 1 && f[u] != 2) f[u] = 2, q.push(u); //首次确定u为必败态
            else if (!deg[u] && f[u] != 2) f[u] = 1, q.push(u); //拆完出边仍未必败的才是必胜态
        }
    }
    
    string ans[] = {"Draw", "Takahashi", "Aoki"};
    for (auto s : edges) {
        cout << ans[f[s]] << '\n';
    }
    
    return 0;
}

F - Deforestation 2307

把 n 棵树砍完,每次选择一棵树 \(i\),花费 \(a_{i-1}+a_i+a_{i+1}\) 然后使 \(a_i\) 变成 \(0\)。问有多少种砍树顺序的花费并列最小