1.13训练赛

A.P7998 猜数

对于一个区间\([L, R]\)中的询问\([l, r]\)交互库必然会让你能确定的区间为
\([L, r - 1]\)\([l + 1, R]\)中的较长者。所以我们应当选取到端点距离相等的区间询问。
在此基础上dp,有

\[dp[i] = \min \limits_{i / 2 <= j <= i - 1}(dp[j] + w(j, i)) \]

其中\(w(j, i) = \frac{1}{i - 2(i - 1 - j)} = \frac{1}{2j + 2 - i}\)
满足四边形不等式,所以这是一个决策单调性dp,可以用双端队列+二分\(nlogn\)实现。
如果你不会,可以尝试\(n^2\) dp后分段打表。

inline double f(int l, int r) {
    ll w = l * 2 + 2 - r;
    return w > 0 ? 1.0 / w + dp[l] : 1e9;
}

inline int bs(int x, int y) { // 返回x相对于y的边界
    int l = x, r = x + 1 << 1;
    while (l < r - 1) {
        m = l + r >> 1;
        f(x, m) < f(y, m) ? l = m : r = m;
    }
    // 在l及其之前从x转移更优,之后从y转移更优
    return l;
}

signed main() {
    ios::sync_with_stdio(0);
    cin >> n, l = 1, r = n;
    // q中存的是决策点,p中存的是决策点相对于下一个决策点的边界
    fu(i, 2, n) {
        // 队头的决策点范围已经遍历完
        if (h < t && p[h] < i)
            ++h;
        // 通过队头确定dp[i]
        dp[i] = f(q[h], i), x[i] = q[h];
        // 新加入的i可以覆盖掉已有决策点
        while (h < t && p[t - 1] >= bs(q[t], i))
            --t;
        // 加入i
        p[t] = bs(q[t], i), q[++t] = i;
    }
    while (l < r) {
        m = r - l + 1;
        cout << "? " << r - x[m] << ' ' << l + x[m] << '\n';
        cin >> a >> b;
        if (b ^ 1)
            b ? r = a - 1 : l = a + 1;
        else
            l = r = a;
    }
    cout << "! " << l;
}

B.CF1437C Chef Monocarp

\(dp[i][j]\)代表前i个位置放了前j道菜,那么

\[dp[i][j] = min(dp[i - 1][j - 1] + abs(t[j] - i), dp[i - 1][j]) \]

int main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> t;
    while (t--) {
        cin >> n;
        memset(dp, 60, sizeof dp), dp[0][0] = 0;
        fu(i, 1, n) cin >> x[i], dp[i][0] = 0;
        sort(x + 1, x + n + 1);
        fu(i, 1, 300) fu(j, 1, min(i, n)) dp[i][j] = 
            min(dp[i - 1][j - 1] + abs(x[j] - i), dp[i - 1][j]);
        k = dp[n][n];
        fu(i, n + 1, 300) mn(k, dp[i][n]);
        cout << k << '\n';
    }
}

也可以用网络流\二分图匹配,第\(i\)个点连\(i \sim n + (i - 1 >> 1)\)

inline void add(ll u, ll v, ll C, ll W) {
    ne[cnt] = he[u], to[cnt] = v, c[cnt] = C, w[cnt] = W, he[u] = cnt++;
    ne[cnt] = he[v], to[cnt] = u, c[cnt] = 0, w[cnt] = -W, he[v] = cnt++;
}

inline bool dij(ll t) {
    __gnu_pbds::priority_queue<pll, greater<pll>> q;
    memset(d + 1, 14, t << 3), memset(vis + 1, 0, t << 3);
    q.push({0, 0}), m = t + 1;
    while (sz(q)) {
        auto [W, u] = q.top();
        q.pop();
        if (d[u] < W)
            continue;
        --m, vis[u] = 1;
        if (!m)
            break;
        for (ll i = he[u]; ~i; i = ne[i])
            if (c[i] && !vis[to[i]]) {
                ll v = to[i], tw = d[u] + w[i] + h[u] - h[v];
                if (tw < d[v])
                    d[v] = tw, pv[v] = u, pe[v] = i, q.push({d[v], v});
            }
    }
    return d[t] < inf;
}

inline void mcmf(ll t) {
    F = C = 0;
    while (dij(t)) {
        fu(i, 1, t) if (h[i] < inf) h[i] += d[i];
        ll f = inf;
        for (ll u = t; u; u = pv[u])
            mn(f, c[pe[u]]);
        F += f, C += h[t] * f;
        for (ll u = t; u; u = pv[u])
            c[pe[u]] -= f, c[pe[u] ^ 1] += f;
    }
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> t;
    while (t--) {
        cin >> n, cnt = 0;
        memset(he, -1, sizeof he);
        fu(i, 1, n) cin >> x[i];
        sort(x + 1, x + n + 1);
        ll t = 2 * n + (n - 1 >> 1) + 1;
        fu(i, n + 1, t - 1) add(i, t, 1, 0);
        fu(i, 1, n) {
            add(0, i, 1, 0);
            fu(j, i, n + (i - 1 >> 1)) add(i, n + j, 1, abs(j - x[i]));
        }
        mcmf(t);
        cout << C << '\n';
    }
}

C.CF1438C Engineer Artem

利用奇偶性构造,也可以2-sat。

int main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> t;
    while (t--) {
        cout << '\n';
        cin >> n >> m;
        int x[n + 1][m + 1], F = 0, f = 0;
        fu(i, 1, n) {
            F ^= 1, f = F;
            fu(j, 1, m) {
                cin >> x[i][j], x[i][j] += x[i][j] & 1 ^ (f ^= 1);
            }
        }
        fu(i, 1, n) {
            fu(j, 1, m) cout << x[i][j] << ' ';
            cout << '\n';
        }
    }
}

D.CF1467B Hills And Valleys

简单的写法是枚举当前点修改对左右点的影响,也可以贪心(容易漏情况)。

inline bool ck(int l, int m, int r) {
    if (l < 1 || r > n)
        return 0;
    return ll(x[m] - x[l]) * (x[m] - x[r]) > 0;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> t;
    while (t--) {
        cin >> n, ans = 0;
        fu(i, 1, n) cin >> x[i];
        fu(i, 1, n) y[i] = ck(i - 1, i, i + 1), ans += y[i];
        m = min(ans, 1);
        fu(i, 2, n - 1) if (y[i]) {
            if (y[i - 1] && y[i + 1]) {
                m = 3;
                break;
            }
            if (y[i - 1] && (!ck(i - 3, i - 2, i) ||
                             !ck(i - 2, i - 1, i + 1) || 
                             !ck(i - 1, i + 1, i + 2)))
                m = 2;
        }
        cout << ans - m << '\n';
    }
}

E.CF1621E New School

题意转化一下就是给定长度为\(m\)的数组\(a,b\),每次在原\(a\)数组中最多改变一个值,
问对\(a,b\)排序后是否满足\(\forall i \in [1, m], a_i <= b_i\),注意到修改
后被修改数以外的数的位置最多\(±1\),预处理一下就可以了。

int main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> t;
    while (t--) {
        cin >> n >> m, r[m + 1] = 1;
        fu(i, 1, n) cin >> x[i];
        sort(x + 1, x + n + 1), copy(x + 1 + n - m, x + n + 1, x + 1);
        x[m + 1] = 0;
        vector<ll> v[m + 1];
        fu(i, 1, m) {
            cin >> y[i], z[i] = 0;
            v[i].resize(y[i] + 1);
            fu(j, 1, y[i]) cin >> v[i][j], z[i] += v[i][j];
            fu(j, 1, y[i]) v[i][j] = (z[i] - v[i][j] + y[i] - 2) / (y[i] - 1);
            w[i] = z[i] = (z[i] + y[i] - 1) / y[i];
        }
        sort(w + 1, w + m + 1);
        fu(i, 1, m) {
            l[i] = l[i - 1] & w[i] <= x[i];
            L[i] = w[i] <= x[i - 1] ? L[i - 1] + 1 : 0;
        }
        fd(i, m, 1) {
            r[i] = r[i + 1] & w[i] <= x[i];
            R[i] = w[i] <= x[i + 1] ? R[i + 1] + 1 : 0;
        }
        fu(i, 1, m) {
            ll P = lower_bound(w + 1, w + m + 1, z[i]) - w;
            string a;
            fu(j, 1, y[i]) {
                ll p = lower_bound(w + 1, w + m + 1, v[i][j]) - w;
                if (p > P)
                    cout << char('0' + (r[p] && v[i][j] <= x[p - 1] 
                    && p - 1 - L[p - 1] <= P && l[P - 1]));
                else
                    cout << char('0' + (r[P + 1] && p + R[p] >= P 
                    && v[i][j] <= x[p] && l[p - 1]));
            }
        }
        cout << '\n';
    }
}

F.P7943 CONsecutive and CONcat

把贡献分为字符串内部的贡献以及与其它字符串拼接的贡献。对于某一字符
的拼接贡献,可以表示成后缀+相同字母串+前缀,即

\[\sum_{s = 0}^{m - 1} \sum_{a = 0}^{cnt} \sum_{p = 0}^{m - 1} (suf(s) * pre(p) - same(s, p)) * A(cnt, a) * max(0, n - 1 - a)! *max(0, s + l * m + p - k + 1)\]

inline ll A(ll n, ll m) {
    return fac[n] * ifac[n - m] % M;
}

inline ll cal(ll c, ll s, ll l, ll p) {
    ll r = suf[c][s] * pre[c][p] - same[c][s][p];
    if (l < n && !s && !p)
        --r; // 如果中间长度不够n,那么不能同时取左右端点
    r = r % M * A(all[c], l) % M * fac[max(0ll, n - 1 - l)] % M;
    return r * max(0ll, s + p + l * m - k + 1) % M;
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> n >> m >> k, fac[0] = ifac[0] = 1;
    fu(i, 1, n) fac[i] = fac[i - 1] * i % M;
    fu(i, 2, n) iv[i] = (M - M / i) * iv[M % i] % M;
    fu(i, 1, n) ifac[i] = ifac[i - 1] * iv[i] % M;
    fu(c, 'a', 'z')++ suf[c][0], ++pre[c][0]; // 左右端点
    fu(i, 1, n) {
        cin >> s, f = s[0], b = s[m - 1];
        // 前后边界
        l = s.find_first_not_of(f);
        r = s.find_last_not_of(b);
        if (~l) {
            t = 0, c = 1; // 字符串内部贡献
            fu(i, l + 1, r) {
                s[i] ^ s[i - 1] ? c = 1 : ++c;
                t += c >= k;
            }
            ans += t * fac[n] % M, r = m - r - 1;
            fu(c, 'a', 'z') { // 比如后缀字母是a,那么对于b-z就是suf[0]
                ++pre[c][c ^ f ? 0 : l], ++suf[c][c ^ b ? 0 : r];
                ++same[c][c ^ b ? 0 : r][c ^ f ? 0 : l];
            }
        } else { // 字符串由相同字母构成
            ++all[f];
            fu(c, 'a', 'z') if (c ^ f) {
                ++pre[c][0], ++suf[c][0], ++same[c][0][0];
            }
        }
    }
    ans %= M;
    fu(c, 'a', 'z') fu(i, 0, m - 1) fu(j, 0, all[c]) fu(k, 0, m - 1)
        ans += cal(c, i, j, k);
    cout << ans % M;
}

G.P1224 向量内积

注意到k = 2 or 3,先考虑k = 2,以下运算都在模k意义下进行,两个向量的点积只能为0或1,
对应是或不是k的倍数,每次让第i个向量点乘前面i - 1个向量的和,如果结果为i - 1,
说明有满足要求的有0,2,4...个,否则至少有一个,暴力把它找出来就行。考虑至少有一对满足
要求的向量时出错的概率,加入一个与这两个向量的点分别为0,1的向量,随机排列下出错的概率
为1/3,所以打乱序列做几次就行,k = 3时将点积平方就可以分成0,1。此时计算公式为\(\sum_i \sum_j a_i a_j b_i b_j\)

inline int ck(const valarray<valarray<int>> &v, int p) {
    fu(i, 0, p - 1) {
        a = (v[x[i]] * v[x[p]]).sum();
        k ^ 2 ? a = a * a % 3 : 0;
        if (~a & 1)
            return cout << min(++x[p], ++x[i]) << ' ' << max(x[p], x[i]), 0;
    }
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> n >> m >> k;
    valarray<valarray<int>> v(valarray<int>(m), n);
    fu(i, 0, n - 1) fu(j, 0, m - 1) cin >> v[i][j], v[i][j] %= k;
    iota(x, x + n, 0);
    fu(_, 1, 3) {
        shuffle(x, x + n, default_random_engine());
        if (k ^ 3) {
            auto t = v[x[0]];
            fu(i, 1, n - 1) {
                if ((v[x[i]] * t).sum() & 1 ^ i & 1)
                    return ck(v, i);
                t += v[x[i]];
            }
        } else {
            valarray<valarray<int>> t(m);
            fu(i, 0, m - 1) t[i] = v[x[0]][i] * v[x[0]];
            fu(i, 1, n - 1) {
                a = 0;
                fu(j, 0, m - 1) a += (v[x[i]] * t[j]).sum() * v[x[i]][j];
                if (a % 3 ^ i % 3)
                    return ck(v, i);
                fu(j, 0, m - 1) t[j] += v[x[i]][j] * v[x[i]];
            }
        }
    }
    cout << "-1 -1";
}

H.P1231 教辅的组成

比较裸的网络流。

inline bool bfs() {
    memset(d + 1, 0, t << 2);
    queue<int> q({0});
    while (sz(q)) {
        int u = q.front();
        q.pop();
        for (auto [v, c] : g[u])
            if (!d[v] && c)
                d[v] = d[u] + 1, q.emplace(v);
    }
    return d[t];
}

int dfs(int u = 0, int c = 1e9) {
    if (u == t)
        return c;
    int F = 0, f;
    for (auto &i = cur[u]; c && i != g[u].end(); ++i) {
        auto &[v, w] = *i;
        if (d[v] == d[u] + 1 && w) {
            f = dfs(v, min(c, w));
            w -= f, c -= f, F += f, g[v][u] += f;
        }
    }
    return F;
}

inline void dinic() {
    while (bfs()) {
        fu(i, 0, t) cur[i] = g[i].begin();
        F += dfs();
    }
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> n1 >> n2 >> n3, n = n1 + n2, n0 = n + n2;
    cin >> m, t = n0 + n3 + 1;
    fu(i, 1, n1) g[0][i] = 1, g[i][0] = 0;
    fu(i, 1, n2) g[i + n1][i + n] = 1, g[i + n][i + n1] = 0;
    fu(i, 1, n3) g[i + n0][t] = 1, g[t][i + n0] = 0;
    fu(i, 1, m) cin >> x >> y, g[y][x + n1] = 1, g[x + n1][y] = 0;
    cin >> m;
    fu(i, 1, m) cin >> x >> y, g[x + n][y + n0] = 1, g[y + n0][x + n] = 0;
    dinic(), cout << F;
}
posted @ 2022-01-22 13:36  lemu  阅读(54)  评论(0编辑  收藏  举报
4