T1:Legendary Players

模拟

代码实现
table = '''
tourist 3858
ksun48 3679
Benq 3658
Um_nik 3648
apiad 3638
Stonefeang 3630
ecnerwala 3613
mnbvmar 3555
newbiedmy 3516
semiexp 3481
'''

s = input()
for line in table.strip().split('\n'):
    user, rating = line.split()
    if user == s:
        print(rating)

T2:Measure

模拟

代码实现
#include <bits/stdc++.h>

using namespace std;

int main() {
    int n;
    cin >> n;
    
    string s(n+1, '-');
    for (int j = 9; j >= 1; --j) {
        if (n%j != 0) continue;
        for (int i = 0; i <= n; i += n/j) {
            s[i] = '0'+j;
        }
    }
    
    cout << s << '\n';
    
    return 0;
}

T3:False Hope

简单题意:

现在有一个 \(3 \times 3\) 的方格 \(c\),每一个格子中有一个元素,但是刚开始你不知道所有方格中权值的情况。

现在你将要进行 \(9\) 次操作,每一次操作你将要选择一个没有被翻开的位置。

如果在一次操作中,在将要翻转的格子的横向或纵向或对角线上已经有两个已经翻开的格子并且满足同一方向上有两个格子上的数相同,那么你就会感到沮丧。

求:以任意顺序翻开所有格子,你不会感到沮丧的概率。

分析:

暴力枚举 \(9!\) 种顺序即可

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using P = pair<int, int>;

int main() {
    vector<int> c(9);
    rep(i, 9) cin >> c[i];
    
    vector<int> p(9);
    rep(i, 9) p[i] = i;
    
    int cnt = 0, tot = 0;
    do {
        bool ok = true;
        auto f = [&](int i, int j, int k) {
            vector<P> d;
            d.emplace_back(p[i], c[i]);
            d.emplace_back(p[j], c[j]);
            d.emplace_back(p[k], c[k]);
            sort(d.begin(), d.end());
            if (d[0].second == d[1].second) ok = false;
        };
        f(0, 1, 2);
        f(3, 4, 5);
        f(6, 7, 8);
        f(0, 3, 6);
        f(1, 4, 7);
        f(2, 5, 8);
        f(0, 4, 8);
        f(2, 4, 6);
        if (ok) cnt++;
        tot++;
    } while (next_permutation(p.begin(), p.end()));
    
    double ans = 1.*cnt/tot;
    printf("%.10f\n", ans);
    
    return 0;
}

T4:Minimum Width

二分

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

int main() {
    int n, m;
    cin >> n >> m;
    
    vector<int> l(n);
    rep(i, n) cin >> l[i];
    
    ll wa = 0, ac = 1e15;
    while (abs(ac-wa) > 1) {
        ll wj = (ac+wa)/2;
        auto f = [&](ll w) {
            int line = 0;
            ll rem = 0;
            rep(i, n) {
                if (rem >= l[i]+1) {
                    rem -= l[i]+1;
                }
                else {
                    line++;
                    rem = w-l[i];
                    if (rem < 0) return false;
                }
            }
            return line <= m;
        };
        if (f(wj)) ac = wj; else wa = wj;
    }
    
    cout << ac << '\n';
    
    return 0;
}

T5:Bus Stops

注意到 \(p_i \leqslant 8\),根据抽屉原理,一定会存在很多重复。考虑周期性,注意到,时间每经过 \(\operatorname{LCM}(1, 2, 3, 4, 5, 6, 7, 8) = 840\),公交车的到达时间都是相同的,因此移动所需的时间也是相同的。 所以只需 \(\mathcal{O}(n)\) 模拟出发时间为 \(0 \sim 839\) 的答案,就能做到 \(\mathcal{O}(1)\) 询问了。

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

int main() {
    int n, x, y;
    cin >> n >> x >> y;
    n--;
    
    vector<int> p(n), t(n);
    rep(i, n) cin >> p[i] >> t[i];
    
    int m = 840;
    vector<ll> d(m);
    rep(si, m) {
        ll now = si+x;
        rep(i, n) {
            while (now%p[i]) now++;
            now += t[i];
        }
        now += y;
        d[si] = now-si;
    }
    
    int q;
    cin >> q;
    rep(qi, q) {
        ll now;
        cin >> now;
        now += d[now%m];
        cout << now << '\n';
    }
    
    return 0;
}

T6:Fighter Takahashi

注意到,如果当前能够选择打倒敌人和嗑药,显然选择优先打倒敌人一定不亏。因为先加后乘得到的战斗力一定大于先乘后加得到的战斗力。另外,在有多个敌人可以打倒的时候,无论选择优先打倒哪个敌人,高桥最后的战斗力都是一样的。

因此,能打倒的敌人立刻打倒是最优策略。如果没有药物,可以用优先队列来维护当前可以到达的敌人所在的点以及它的战斗力贪心地解决。

现在考虑有药的情况,假设有 \(M\) 个药

根据嗑药顺序,打倒敌人的时机也会发生变化,如果固定嗑药顺序的话,最佳战略就是前面提到的贪心。也就是说,固定嗑药顺序 \((x, y, z, \cdots)\) 的策略为

  • 不嗑药能打倒多少敌人就打倒多少
  • \(x\) 能拿就拿
  • 不嗑药能打倒多少敌人就打倒多少
  • \(y\) 能拿就拿
  • \(\cdots\)

那么,本题通过枚举这 \(M!\) 种拿药顺序就能解决!但这种做法的时间复杂度为 \(\mathcal{O}(M!N\log N)\),容易超时。

如果想降低枚举所有排列的时间复杂度,就需要用到状压dp。

当使用固定的药物集合时,打倒哪个敌人仅由高桥的战斗力决定,高桥的战斗力越高的话,能打倒的敌人的集合单调增大。
dp[S] 表示能使用的药的集合为 \(S\) 时高桥能达到的最大战斗力
时间复杂度就降到了 \(\mathcal{O}(M2^MN\log N)\)

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;
using P = pair<int, int>;

int main() {
    int n;
    cin >> n;
    
    vector<vector<int>> to(n);
    vector<int> es(n), eg(n);
    vector<int> mul(n), id(n);
    int m = 0;
    vector<int> dvs;
    es[0] = 0; eg[0] = 1;
    for (int i = 1; i < n; ++i) {
        int p, t, s, g;
        cin >> p >> t >> s >> g;
        --p;
        to[p].push_back(i);
        if (t == 1){
            es[i] = s; eg[i] = g;
        }
        else {
            es[i] = -1;
            mul[i] = g;
            dvs.push_back(i);
            id[i] = m++;
        }
    }
    
    int m2 = 1<<m;
    const ll INF = 1001001001;
    vector<ll> dp(m2, -1);
    using PQ = priority_queue<P, vector<P>, greater<P>>;
    vector<PQ> mem_q(m2);
    vector<int> mem_nb(m2);
    
    auto push = [&](int s, ll p, PQ q, int nb) {
        while (q.size() and q.top().first <= p) {
            int v = q.top().second; q.pop();
            if (es[v] != -1) p += eg[v];
            for (int u : to[v]) {
                if (es[u] == -1) nb |= 1<<id[u];
                else q.emplace(es[u], u);
            }
        }
        if (dp[s] < p) {
            dp[s] = min(p, INF);
            mem_q[s] = q; mem_nb[s] = nb;
        }
    };
    
    {
        PQ q;
        q.emplace(0, 0);
        push(0, 0, q, 0);
    }
    
    rep(s, m2) {
        int nb = mem_nb[s];
        rep(i, m) if (nb>>i&1) {
            int v = dvs[i];
            ll p = dp[s];
            PQ q = mem_q[s];
            q.emplace(0, v);
            p *= mul[v]; p = min(p, INF);
            push(s|1<<i, p, q, nb^1<<i);
        }
    }
    
    if (dp[m2-1] != -1 and mem_q[m2-1].empty()) puts("Yes");
    else puts("No");
    
    return 0;
}

T7:Counting Shortest Paths

补图上的的最短路计数
std::set 维护未访问过的点并进行 \(\operatorname{bfs}\)

对于当前点 \(u\),遍历集合 \(S\) 中的所有点,跳过和 \(G\) 有连边的点,更新其他点到点 \(u\) 的距离,并将点 \(u\) 从集合 \(S\) 中删除

关于最短路计数,这里可以开一个数组用来维护分层图上每一层上的点集,然后在分层图上做一遍 \(dp\) 即可

代码实现
#include <bits/stdc++.h>
#if __has_include(<atcoder/all>)
#include <atcoder/all>
using namespace atcoder;
#endif
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using mint = modint998244353;
using US = unordered_set<int>;

int main() {
    int n, m;
    cin >> n >> m;
    
    vector<US> to(n);
    rep(i, m) {
        int a, b;
        cin >> a >> b;
        --a; --b;
        to[a].insert(b);
        to[b].insert(a);
    }
    
    const int INF = 1001001001;
    vector<int> dist(n, INF);
    queue<int> q;
    dist[0] = 0; q.push(0);
    US s;
    for (int i = 1; i < n; ++i) s.insert(i);
    while (q.size()) {
        int v = q.front(); q.pop();
        vector<int> del;
        for (int u : s) {
            if (to[v].count(u)) continue;
            dist[u] = dist[v]+1;
            del.push_back(u);
            q.push(u);
        }
        for (int u : del) s.erase(u);
    }
    
    if (dist[n-1] == INF) {
        puts("-1");
        return 0;
    }
    
    vector<mint> dp(n);
    dp[0] = 1;
    vector<vector<int>> vs(n);
    rep(i, n) if (dist[i] != INF) vs[dist[i]].push_back(i);
    rep(i, n-1) {
        mint tot;
        for (int v : vs[i]) tot += dp[v];
        for (int v : vs[i+1]) {
            dp[v] = tot;
            for (int u : to[v]) {
                if (dist[u] == i) dp[v] -= dp[u];
            }
        }
    }
    
    cout << dp[n-1].val() << '\n';
    
    return 0;
}