牛客练习赛78

牛客练习赛78

A CCA的词典

签到, 注意 s[0] == s[1]

int main() {
    IOS; cin >> n;
    multiset<string> st;
    rep (i ,1, n) {
        string s; cin >> s;
        st.insert(s);
        if (s.size() != 1 && s[0] != s[1]) swap(s[0], s[1]), st.insert(s);
    }
    cin >> m;
    rep (i, 1, m) {
        string s; cin >> s;
        cout << st.count(s) << '\n';
    }
    return 0;
}

B CCA的搬运

n <= 2e3, 暴力的复杂度, 签到题, 贪心模拟题意

int n, m, _, k, cas;
int a[N], b[N];
ll ans, cur;
deque<PII> c;
 
int main() {
    IOS; cin >> n >> m;
    rep (i, 1, n) cin >> a[i];
    unordered_set<int> st;
    rep (i, 1, m) {
        cin >> b[i];
        if (st.count(b[i])) continue;
        c.push_back({a[b[i]], b[i]}); st.insert(b[i]);
    }
    rep (i, 1, m) {
        int cur = 0;
        for (auto it = c.begin(); it != c.end(); ++it) {
            if (it->se == b[i]) { c.erase(it); break; }
            cur += it->fi;
        }
        ans += cur; c.push_front({a[b[i]], b[i]});
    }
    cout << ans;
    return 0;
}

C CCA的子树

树形dp d[i] 表示以i为根的子树中最大的权值和, f[i]表示以i为根的树的权值和

ll a[N], d[N], f[N], ans = -2e18;
VI h[N];

void dfs(int x, int fa) {
    for (auto &y : h[x]) if (y != fa) {
        dfs(y, x);
        if (d[x] != -1e18) umax(ans, d[x] + d[y]);
        umax(d[x], d[y]); f[x] += f[y];
    }
    f[x] += a[x]; umax(d[x], f[x]);
}

int main() {
    IOS; cin >> n;
    rep (i, 1, n) cin >> a[i], d[i] = -1e18;
    rep (i, 2, n) {
        int u, v; cin >> u >> v;
        h[u].pb(v); h[v].pb(u);
    }
    dfs(1, 0);
    if (ans == -2e18) cout << "Error";
    else cout << ans;
    return 0;
}

D CCA的小球

二项式反演

设有m对小球颜色相同

\(g(i)\)表示至少有 i 对颜色相同的小球相邻的排列方案数 \(g(i) = \frac {C_m^i \times (n - i)!}{2^{m - i}}\)

\(f(i)\)恰有 i 对颜色相同的小球相邻的排列方案数

注意到\(g(i) = \sum_{j=i}^n C_j^i \times f(i)\)

由二项式反演得 \(f(i) = \sum_{j=i}^n (-1)^{n-j} C_j^i \times g(i)\)

\(ans = f(0) = \sum_{j=0}^n (-1)^{n-j} C_j^0 \times \frac {C_m^j \times (n - j)!} {2^{m - j}}\)

ll n, m, _, k, cas;
ll a[N], inv[N], fac[N], facinv[N], qp[N], ans;
unordered_set<ll> st;

ll C(int n, int m) { return fac[n] * facinv[m] % mod * facinv[n - m] % mod; }

void init(int n) {
    qp[0] = inv[0] = inv[1] = fac[0] = fac[1] = facinv[0] = facinv[1] = 1;
    rep (i, 2, n) {
        fac[i] = fac[i - 1] * i % mod;
        inv[i] = (ll)(mod - mod / i) * inv[mod % i] % mod;
        facinv[i] = facinv[i - 1] * inv[i] % mod;
    } qp[1] = inv[2];
    rep (i, 2, n) qp[i] = qp[i - 1] * inv[2] % mod;
}

int main() {
    IOS; cin >> n; init(1e6);
    rep (i, 1, n) {
        cin >> a[i];
        if (st.count(a[i])) ++m, st.erase(st.find(a[i]));
        else st.insert(a[i]);
    }
    rep (i, 0, m)
        if (i & 1) ans = (ans - C(m, i) * fac[n - i] % mod * qp[m - i] % mod) % mod;
        else ans = (ans + C(m, i) * fac[n - i] % mod * qp[m - i] % mod) % mod;
    cout << (ans + mod) % mod;
    return 0;
}

E CCA的小球

可以反转意味着选可以将原本不相邻得两个区间变得相邻,

设区间内无相同的a[i], 两个相邻区间和为 x, y

则 ans = max(x ^ y) (x & y == 0)

怎么预处理区间和呢? 注意到每 24 个数必出现相同的 a[i] 故可暴力处理找出存在的区间的大小, 复杂度O(n * 24)

x ^ y, 在确定 x 的情况下, 则找i的补集 \((1 << 24) - 1 ^ x\) 集合内部的最大值 y,

暴力处理集合内部不可取, 注意到

对于每个 y 属于 \(y ^ (1 << j)\)集合, 其中 \(1 << j \& y == 0\), 则线性从小集合可推大集合, 复杂度为 O((1 << 24) * 24)

最后枚举至于, 找O(1)补集内最大值即可, 复杂度 O(1 << 24)

const int N = 1e5 + 5, M = 1 << 24;

int n, m, _, k, cas;
int a[N], ans, mx[M];

int main() {
    IOS; cin >> n;
    rep(i, 1, n) {
        cin >> a[i]; mx[m = a[i]] = 1;
        per(j, i - 1, 1) if (m & a[j]) break; else m ^= a[j], mx[m] = m;
    }
    rep (i, 1, M - 1) if (mx[i]) rep (j, 0, 23) if (!(i >> j & 1)) umax(mx[i ^ 1 << j], mx[i]);
    rep (i, 1, M - 1) umax(ans, mx[i] ^ mx[M - 1 ^ i]);
    cout << ans;
    return 0;
}

CCA的序列

设当前长度为i 以 数字k 结尾的子序列有 f[i][k] 个, 当前总的子序列有 sum[i] 个

注意sum[0] = 1, 空串默认算一个子序列

则当第 i 个位置添加数字 k, 则出了 j != k, f[i][j] = f[i- 1][j];

f[i][k] = sum[i - 1] //在 i - 1 长度的所有子序列(包括空串)后添加一个数字k

sum[i] = (sum[i - 1] - f[i - 1][k]) + sum[i - 1] = 2 * sum[i - 1] - f[i - 1][k]

要想sum[i] 最大, 明显是添加 min(f[i - 1][k]) 的数字k, 且注意到

f[i - 1][k] 添加 k 之后 f[i][k] = sum[i - 1], 变成了当前长度中子序列最多的数字

即每次都是从最小直接变成最大, 所以添加 m 个数的时候是个循环, 则可用矩阵加速

设长度为 i - 1 的状态为 $ (min_1(f[i - 1][j]), min_2(f[i - 1][j]), ..., min_k(f[i - 1][j]), sum[i - 1])$

则通过 × 矩阵C 可获得长度为 i 的状态

\(\begin{matrix} 0 & 0 & ... & 0 & 0 & -1\\ 1 & 0 & ... & 0 & 0 & 0\\ 0 & 1 & ... & 0 & 0 & 0\\ & & ... & & & \\ 0 & 0 & ... & 1 & 0 & 0\\ 0 & 0 & ... & 0 & 1 & 0\\ 0 & 0 & ... & 0 & 0 & 2 \end{matrix}\)

输出最终 sum[n + m] - 1 (减去空序列)即可

const int N = 1e6 + 5, M = 1e2 + 5, mod = 1e9 + 7;

struct Matrix {
    vector<vector<ll>> a;
    Matrix (int n, int m) { a.resize(n, vector<ll>(m, 0)); }
    Matrix operator*(Matrix& b) {
        Matrix c(a.size(), b.a[0].size());
        rep (i, 0, c.a.size() - 1) rep (j, 0, c.a[0].size() - 1) rep (k, 0, b.a.size() - 1)
            c.a[i][j] = (c.a[i][j] + a[i][k] * b.a[k][j] % mod) % mod;
        return c;
    }
};

ll n, m, _, k, cas;
int pre[M], a[N], rk[M];
ll f[N + M];

Matrix qpow(Matrix a, ll b) {
    Matrix ans(a.a.size(), a.a[0].size());
    rep (i, 0, a.a.size() - 1) ans.a[i][i] = 1;
    for (; b; b >>= 1, a = a * a) if (b & 1) ans = ans * a;
    return ans;
}

int main() {
    IOS; cin >> n >> m >> k; f[0] = 1; Matrix b(1, k + 1), c(k + 1, k + 1);
    rep (i, 1, n) {
        cin >> a[i];
        if (pre[a[i]]) f[i] = (f[i - 1] * 2 - f[pre[a[i]] - 1] + mod) % mod;
        else f[i] = f[i - 1] * 2 % mod;
        pre[a[i]] = i;
    }
    rep (i, 1, k) rk[i] = i, c.a[i][i - 1] = 1;
    sort(rk + 1, rk + k + 1, [&](int a, int b) { return pre[a] < pre[b]; });
    rep (i, n + 1, n + k) {
        if (pre[rk[i - n]]) f[i] = (f[i - 1] * 2 - f[pre[rk[i - n]] - 1] + mod) % mod;
        else f[i] = f[i - 1] * 2 % mod;
        b.a[0][i - n - 1] = f[i - 1];
    }
    if (n + m <= n + k) return cout << (f[n + m] - 1 + mod) % mod, 0;
    b.a[0][k] = f[n + k]; c.a[0][k] = 2, c.a[k][k] = mod - 1;
    cout << ((b * qpow(c, m - k)).a[0][k] - 1 + mod) % mod;
    return 0;
}
posted @ 2021-03-13 16:26  洛绫璃  阅读(28)  评论(0编辑  收藏  举报