A. AtCoder Line

判断 \(z\) 是否出现在 \(x\)\(y\) 之间即可

代码实现
n, x, y, z = map(int, input().split())
if x > y:
    x, y = y, x
if x < z < y:
    print('Yes')
else:
    print('No')

B.Typing

模拟

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

using namespace std;

int main() {
    string s, t;
    cin >> s >> t;
    
    int si = 0;
    rep(i, t.size()) {
        if (s[si] == t[i]) {
            cout << i+1 << ' ';
            si++;
        }
    }
    
    return 0;
}

C. Standing On The Shoulders

\(\sum a +\max(b_i-a_i)\)

代码实现
#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;
    cin >> n;
    
    vector<int> a(n), b(n);
    rep(i, n) cin >> a[i] >> b[i];
    
    int mx = 0;
    rep(i, n) mx = max(mx, b[i]-a[i]);
    
    ll ans = mx;
    rep(i, n) ans += a[i];
    
    cout << ans << '\n';
    
    return 0;
}

D. Permutation Subsequence

可以开一个数组 q 用来记录数 \(p_i\) 所对应的下标
维护一个大小为 \(k\) 且数值连续的滑动窗口,我们需要快速找到这个窗口中的每个数所对应的下标数组的最大值和最小值,这个可以用 std::setbegin()rbegin() 实现

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

using namespace std;

int main() {
    int n, k;
    cin >> n >> k;
    
    vector<int> p(n);
    rep(i, n) cin >> p[i];
    rep(i, n) p[i]--;
    
    vector<int> q(n);
    rep(i, n) q[p[i]] = i;
    
    int ans = n;
    set<int> st;
    rep(i, n) {
        st.insert(q[i]);
        if (st.size() > k) st.erase(q[i-k]);
        if (st.size() == k) {
            int now = *st.rbegin() - *st.begin();
            ans = min(ans, now);
        } 
    }
    
    cout << ans << '\n';
    
    return 0;
}

E. Clique Connect

注意到每个集合中只需留下连通的 \(k-1\) 条边即可
然后按 \(c_i\) 排序后,再跑 Kruskal 即可

代码实现
#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 ll = long long;
using E = tuple<int, int, int>;

int main() {
    int n, m;
    cin >> n >> m;
    
    vector<E> es;
    rep(i, m) {
        int k, c;
        cin >> k >> c;
        vector<int> a(k);
        rep(j, k) cin >> a[j];
        rep(j, k) a[j]--;
        rep(j, k-1) {
            es.emplace_back(c, a[j], a[j+1]);
        }
    }
    
    sort(es.begin(), es.end());
    
    dsu uf(n);
    ll ans = 0;
    for (auto [c, a, b] : es) {
        if (uf.same(a, b)) continue;
        uf.merge(a, b);
        ans += c;
    }
    
    if (uf.size(0) != n) ans = -1;
    cout << ans << '\n';
    
    return 0;
}

F. Estimate Order

根据限制条件,我们可以向点 \(b\) 到点 \(a\) 加一条边权为 \(3\) 的有向边,同时向点 \(a\) 到点 \(b\) 加一条边权为 \(-3\) 的有向边,于是就会形成若干个带权的连通分量
对于一个连通分量,可以用二进制数来表示,那么我们将所有的二进制数做合适的移动再将它们按位或起来就能实现 \(n\) 个位上全为 1
考虑状压dp
假设所有连通块对应的二进制数为 \(x_i\)
dp[i][S] 表示加上 \(x_i\) 后能变成二进制数 \(S\)
枚举连通块 \(k\),在其他连通块的所有可能的合法组合中如果不止一种嵌入这个连通块的方案的话,那么这个连通块内的人的排名不能唯一确定

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

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

int pc(int x) { return __builtin_popcount(x); }

int main() {
    int n, m;
    cin >> n >> m;
    
    vector<vector<P>> g(n);
    rep(i, m) {
        int a, b, c;
        cin >> a >> b >> c;
        --a; --b;
        g[a].emplace_back(b, -c);
        g[b].emplace_back(a, c);
    }
    
    vector<bool> used(n);
    vector<int> idx(n);
    vector<vector<int>> vs;
    vector<int> xs;
    rep(i, n) if (!used[i]) {
        auto dfs = [&](auto& f, int v) -> void {
            vs.back().push_back(v);
            used[v] = true;
            for (auto [u, c] : g[v]) {
                if (used[u]) continue;
                idx[u] = idx[v]+c;
                f(f, u);
            }
        };
        vs.push_back(vector<int>());
        dfs(dfs, i);
        int mn = 0;
        for (int v : vs.back()) mn = min(mn, idx[v]);
        for (int v : vs.back()) idx[v] -= mn;
        int x = 0;
        for (int v : vs.back()) x |= 1<<idx[v];
        xs.push_back(x);
    }
    m = xs.size();
    int n2 = 1<<n;
    
    vector<int> ans(n, -1);
    rep(k, m) {
        vector<bool> dp(n2);
        dp[0] = true;
        int num = 0;
        rep(i, m) if (i != k) {
            rep(s, n2) if (pc(s) == num and dp[s]) {
                int x = xs[i];
                while (x < n2) {
                    if ((s&x) == 0) dp[s|x] = true;
                    x <<= 1;
                }
            }
            num += vs[i].size();
        }
        
        int x = xs[k], cnt = 0;
        int offset = 0, i = 0;
        while (x < n2) {
            if (dp[(n2-1)^x]) offset = i, cnt++;
            x <<= 1; i++;
        }
        if (cnt == 1) {
            for (int v : vs[k]) ans[v] = offset+idx[v]+1;
        }
    }
    
    rep(i, n) cout << ans[i] << ' ';
    
    return 0;
}

G. Socks 3

\(S = \sum A_i\)

待求期望等价于求 \(\sum\limits_{k=0}^N\)\(k\) 次袜子且还没结束的概率
\(=\sum\limits_{k=0}^N\)\(k\) 次袜子且还没结束的方案数 \(\times \prod\limits_{0 \leqslant i < k} \frac{1}{S-i}\)

\(F_k\) 表示取 \(k\) 次袜子且还没结束的方案数
那么 \(F_k\) 就是从每种颜色的袜子中取 \(0\) 个或 \(1\) 个总共取 \(k\) 个的方案数,这是不是很像01背包?

写成生成函数的形式就是 \(F_k = [x^k] \prod (1+A_i x)\),这个式子可以用分治NTT计算
另外在选取的袜子之间还需要进行排序,所以要再乘上 \(k!\)

总的时间复杂度为 \(\mathcal{O}(n\log^2 n)\)

代码实现
#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 fps = vector<mint>;

int main() {
	int n;
	cin >> n;
	
	vector<int> a(n);
	rep(i, n) cin >> a[i];
	
	int s = 0;
	rep(i, n) s += a[i];
	
	queue<fps> q;
	rep(i, n) {
	    fps f = {1, a[i]};
	    q.push(f);
	}
	
	while (q.size() > 1) {
	    fps a = q.front(); q.pop();
	    fps b = q.front(); q.pop();
	    q.push(convolution(a, b));
	}
	fps f = q.front();
	
	mint ans, co = 1;
	rep(k, n+1) {
	    ans += f[k]*co;
	    co /= s-k; co *= k+1;
	}
	
	cout << ans.val() << '\n';
	
	return 0;
}