A. Exponential Plant

模拟

代码实现
h = int(input())
now, day = 0, 0
while now <= h:
    now += 1<<day 
    day += 1
print(day)

B. AtCoder Janken 2

模拟

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

using namespace std;

int main() {
    int n;
    cin >> n;
    
    vector<pair<string, int>> user;
    int t = 0;
    rep(i, n) {
        string s; int c;
        cin >> s >> c;
        user.emplace_back(s, c);
        t += c;
    }
    
    sort(user.begin(), user.end());
    
    string ans = user[t%n].first;
    cout << ans << '\n';
    
    return 0;
}

C. AtCoder Magics

其实就是找二维平面上以 \(a_i\) 最大的点结尾的最长严格上升点列

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

using namespace std;

int main() {
    int n;
    cin >> n;
    
    vector<int> a(n), c(n);
    rep(i, n) cin >> a[i] >> c[i];
    
    vector<int> is(n);
    iota(is.begin(), is.end(), 0);
    sort(is.begin(), is.end(), [&](int i, int j) {
        return a[i] > a[j]; 
    });
    
    vector<int> ans;
    for (int i : is) {
        if (!ans.size() or c[ans.back()] > c[i]) {
            ans.push_back(i);
        } 
    }
    
    sort(ans.begin(), ans.end());
    
    cout << ans.size() << '\n';
    for (int i : ans) cout << i+1 << ' ';
    
    return 0;
}

E. Remove Pairs

状压dp
dp[S] 表示状态为从剩下的卡片集合为 \(S\) 开始的胜负

博弈论的基本性质:

  • 一个状态是必败状态,当且仅当它的所有后继都是必胜状态
  • 一个状态是必胜状态,当且仅当它至少有一个后继是必败状态
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;

int main() {
    int n;
    cin >> n;
    
    vector<int> a(n), b(n);
    rep(i, n) cin >> a[i] >> b[i];
    
    int n2 = 1<<n;
    vector<bool> dp(n2);
    rep(s, n2) {
        bool now = false;
        rep(i, n)rep(j, i) if ((s>>i&1) and (s>>j&1)) {
            if (a[i] == a[j] or b[i] == b[j]) {
                if (!dp[s^1<<i^1<<j]) now = true; 
            }
        }
        dp[s] = now;
    }
    
    if (dp[n2-1]) puts("Takahashi");
    else puts("Aoki");
    
    return 0;
}

F. Useless for LIS

可以先求出以每个数结尾的 LIS 的长度
用线段树优化dp可以做到 \(\mathcal{O}(n\log n)\)
那么如何判断一个数出现在整个序列的 LIS 里呢?

  • 可以先求出以每个数 \(x\) 开始的 LIS 的长度
  • 然后判断以 \(x\) 结尾的 LIS 的长度 + 以 \(x\) 开始的 LIS 的长度是否等于整个序列的 LIS 的长度 - 1
代码实现
#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;

// Coodinate Compression
template<typename T=int>
struct CC {
  bool initialized;
  vector<T> xs;
  CC(): initialized(false) {}
  void add(T x) { xs.push_back(x);}
  void init() {
    sort(xs.begin(), xs.end());
    xs.erase(unique(xs.begin(),xs.end()),xs.end());
    initialized = true;
  }
  int operator()(T x) {
    if (!initialized) init();
    return upper_bound(xs.begin(), xs.end(), x) - xs.begin() - 1;
  }
  T operator[](int i) {
    if (!initialized) init();
    return xs[i];
  }
  int size() {
    if (!initialized) init();
    return xs.size();
  }
};

int op(int a, int b) { return max(a, b); }
int e() { return 0; }

void solve() {
    int n;
    cin >> n;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    {
        CC cc;
        rep(i, n) cc.add(a[i]);
        rep(i, n) a[i] = cc(a[i]);
    }
    
    auto get = [&]() {
        vector<int> dp(n);
        segtree<int, op, e> t(n);
        rep(i, n) {
            dp[i] = t.prod(0, a[i])+1;
            t.set(a[i], dp[i]);
        }
        return dp;
    };
    
    vector<int> dl = get();
    rep(i, n) a[i] = n-1-a[i];
    reverse(a.begin(), a.end());
    vector<int> dr = get();
    reverse(dr.begin(), dr.end());
    
    int lis = *max_element(dl.begin(), dl.end());
    vector<int> ans;
    rep(i, n) {
        if (dl[i]+dr[i]-1 == lis) ans.push_back(i);
    }
    
    cout << ans.size() << '\n';
    for (int i : ans) cout << i+1 << ' ';
    cout << '\n';
}

int main() {
    int t;
    cin >> t;
    
    while (t--) solve();
    
    return 0;
}

G. Select Strings

考虑如果 \(S_j\)\(S_i\) 的子串,则将点 \(i\) 向点 \(j\) 连一条有向边,特别地,如果 \(S_i = S_j\),直接跳过,可以发现最后得到的图是一个DAG
我们需要求的就是这个图的最大反链,而最大反链 \(=\) 最小链覆盖
对于最小链覆盖是网络流的经典模型:拆点建二分图,原图的最小链覆盖就是 \(N-\) 二分图的最大匹配,其中 \(N = \sum A_i\)
另外,对于出现 \(S_i\)\(S_j\) 相同的情况,可以考虑取这个字符串对应的 \(\max (A)\)

代码实现
#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;

int main() {
    int n;
    cin >> n;
    
    vector<string> s;
    vector<int> a;
    {
        vector<string> S(n);
        rep(i, n) cin >> S[i];
        map<string, int> mx;
        rep(i, n) {
            int na;
            cin >> na;
            mx[S[i]] = max(mx[S[i]], na);
        }
        for (auto [ns, na] : mx) {
            s.push_back(ns);
            a.push_back(na);
        }
        n = s.size();
    }
    
    int sv = n*2, tv = sv+1;
    mf_graph<ll> g(tv+1);
    const ll INF = 1e18;
    rep(i, n)rep(j, n) if (i != j) {
        if (s[i].find(s[j]) != string::npos) {
            g.add_edge(i, n+j, INF);
        }
    }
    rep(i, n) g.add_edge(sv, i, a[i]);
    rep(i, n) g.add_edge(n+i, tv, a[i]);
    
    ll ans = 0;
    rep(i, n) ans += a[i];
    ans -= g.flow(sv, tv);
    
    cout << ans << '\n';
    
    return 0;
}