牛客小白月赛27

比赛链接:https://ac.nowcoder.com/acm/contest/6874#question

A - 巨木之森

题解

除了起点到终点的路径,其他的路径都会走两遍,所以为了尽可能减少折返的路径,每次应使起点终点所在路径尽可能的长,亦即二者距离尽可能的远。

最长的一条路径为树的直径,根据树的直径的性质,任意一点的最远点一定为树的直径的两个端点之一。

为了寻找树的直径的两个端点A、B,可以从任意一点BFS得到树的直径的一端点A,再以端点A为起点BFS得到直径的另一端点B。

得到端点A、B后,BFS得到每个点距离A、B的距离,从每个点 $i$ 出发遍历全树的花费即为:

$2\ \times$ 所有路径长 $-\ max(dis_{Ai},dis_{Bi})$

对所有点的花费排序,从小到大采用即可。

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m;
    cin >> n >> m;
    vector<vector<pair<int, int>>> G(n);
    int tot = 0;
    for (int i = 0; i < n - 1; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        --u, --v;
        tot += w;
        G[u].emplace_back(v, w);
        G[v].emplace_back(u, w);
    }
    vector<int> disA(n), disB(n);
    function<void(int, vector<int> &)> bfs = [&](int s, vector<int> &dist) {
        vector<bool> vis(n);
        queue<pair<int, int>> que;
        que.emplace(s, 0);
        vis[s] = true;
        while (!que.empty()) {
            int u = que.front().first;
            int dis = que.front().second;
            que.pop();
            dist[u] = dis;
            for (auto i : G[u]) {
                int v = i.first;
                int w = i.second;
                if (!vis[v]) {
                    que.emplace(v, dis + w);
                    vis[v] = true;
                }
            }
        }
    };
    bfs(0, disA);
    int A = max_element(disA.begin(), disA.end()) - disA.begin();
    bfs(A, disA);
    int B = max_element(disA.begin(), disA.end()) - disA.begin();
    bfs(B, disB);
    vector<int> wt(n);
    for (int i = 0; i < n; i++) {
        wt[i] = tot * 2 - max(disA[i], disB[i]);
    }
    sort(wt.begin(), wt.end());
    int ans = 0;
    for (int i = 0; i < n; i++) {
        m -= wt[i];
        if (m >= 0) {
            ++ans;
        } else {
            break;
        }
    }
    cout << ans << "\n";
    return 0;
}

B - 乐团派对

题解

如果每个乐手都可以被分进一队,那么一定有 $max_{a_i} \le n$ 。

将 $a_i$ 排序,为了保证能力值最大的乐手可以被分进一队,先将他及之前共 $a_n$ 个人分为一队。

之后对于前面的 $n - a_n$ 人进行 $dp$,每个人有两种选择:

  • $dp_i = dp_{i - 1}$,加入之前的队伍
  • $dp_i = dp_{i - a_i} + 1$,与前面的 $a_i$ 人另成一队

最终答案即为 $dp_{n - a_n} + 1$ 。

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    sort(a.begin(), a.end());
    if (a[n] > n) {
        cout << -1 << "\n";
        return 0;
    }
    vector<int> dp(n + 1);
    for (int i = 1; i <= n - a[n]; i++) {
        int j = i - a[i];
        dp[i] = max(dp[i - 1], (j >= 0 ? dp[j] + 1 : 0));
    }
    cout << dp[n - a[n]] + 1 << "\n";
    return 0;
}

C - 光玉小镇

题解

因为 T 的数目最多为 15,所以如果枚举所有可能的修理路径,T! 是会超时的,这个时候可以考虑状压 $dp$ 。

首先 BFS 得到每个电线杆离家的距离 $disS$,以及电线杆两两之间的距离 $dis_{ij}$,同时初始化 $dp$ 数组:

for (int i = 0; i < int(T.size()); i++) {
    dp[1 << i][i] = disS[i] = bfs(Sx, Sy, T[i].first, T[i].second);
}

将每个电线杆的修理状态表示为 $dp_{ij}$ 中 $i$ 的一个比特,$j$ 表示为当前状态下最后修的是哪一个电线杆,状态转移方程即为:

for (int i = 0; i < (1 << T.size()); i++) {
    for (int j = 0; j < int(T.size()); j++) {
        if (((1 << j) & i) == 0) continue;
        for (int k = 0; k < int(T.size()); k++) {
            if ((1 << k) & i) continue;
            dp[i | (1 << k)][k] = min(dp[i | (1 << k)][k], dp[i][j] + dis[j][k]);
        }
    }
}

$ans$ 即为:

for (int i = 0; i < int(T.size()); i++) {
    ans = min(ans, dp[(1 << T.size()) - 1][i] + disS[i] + 1LL * int(T.size()) * t);
}

代码

#include <bits/stdc++.h>
using namespace std;
const int dir[4][2] = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};
struct P{
    int x, y, dis;
};
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m, t;
    cin >> n >> m >> t;
    vector<vector<char>> MP(n, vector<char> (m));
    int Sx = -1, Sy = -1;
    vector<pair<int, int>> T;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++) {
            cin >> MP[i][j];
            if (MP[i][j] == 'S') {
                Sx = i, Sy = j;
            }
            if (MP[i][j] == 'T') {
                T.emplace_back(i, j);
            }
        }
    function<bool(int, int)> legal = [&](int x, int y) {
        return 0 <= x and x < n and 0 <= y and y < m and MP[x][y] != '#';
    };
    function<int(int, int, int, int)> bfs = [&](int sx, int sy, int ex, int ey) {
        vector<vector<bool>> vis(n, vector<bool> (m));
        queue<P> que;
        que.push({sx, sy, 0});
        vis[sx][sy] = true;
        while (!que.empty()) {
            int x = que.front().x;
            int y = que.front().y;
            int dis = que.front().dis;
            que.pop();
            if (x == ex and y == ey) {
                return dis;
            }
            for (int i = 0; i < 4; i++) {
                int nx = x + dir[i][0];
                int ny = y + dir[i][1];
                if (legal(nx, ny) and !vis[nx][ny]) {
                    que.push({nx, ny, dis + 1});
                    vis[nx][ny] = true;
                }
            }
        }
        return -1;
    };
    vector<vector<int>> dp(1 << T.size(), vector<int> (T.size(), 1e9));
    vector<int> disS(T.size());
    for (int i = 0; i < int(T.size()); i++) {
        dp[1 << i][i] = disS[i] = bfs(Sx, Sy, T[i].first, T[i].second);
        if (disS[i] == -1) {
            cout << -1 << "\n";
            return 0;
        }
    }
    vector<vector<int>> dis(T.size(), vector<int> (T.size()));
    for (int i = 0; i < int(T.size()); i++) {
        for (int j = i + 1; j < int(T.size()); j++) {
            dis[i][j] = dis[j][i] = bfs(T[i].first, T[i].second, T[j].first, T[j].second);
            if (dis[i][j] == -1) {
                cout << -1 << "\n";
                return 0;
            }
        }
    }
    for (int i = 0; i < (1 << T.size()); i++) {
        for (int j = 0; j < int(T.size()); j++) {
            if (((1 << j) & i) == 0) continue;
            for (int k = 0; k < int(T.size()); k++) {
                if ((1 << k) & i) continue;
                dp[i | (1 << k)][k] = min(dp[i | (1 << k)][k], dp[i][j] + dis[j][k]);
            }
        }
    }
    long long ans = LLONG_MAX;
    for (int i = 0; i < int(T.size()); i++) {
        ans = min(ans, dp[(1 << T.size()) - 1][i] + disS[i] + 1LL * int(T.size()) * t);
    }
    cout << ans << "\n";
    return 0;
}

D - 巅峰对决

题解

线段树维护区间最值,如果某个区间的值连续,那么该区间的最值差一定等于区间长度。

代码

#include <bits/stdc++.h>
#define lson i << 1, l, mid
#define rson i << 1 | 1, mid + 1, r
#define mid ((l + r) >> 1)
using namespace std;
constexpr int N = 1e5 + 100;
int mi[N << 2], mx[N << 2], a[N];
void pushup(int i) {
    mi[i] = min(mi[i << 1], mi[i << 1 | 1]);
    mx[i] = max(mx[i << 1], mx[i << 1 | 1]);
}
void build(int i, int l, int r) {
    if (l == r) {
        mi[i] = mx[i] = a[l];
        return;
    }
    build(lson);
    build(rson);
    pushup(i);
}
void update(int i, int l, int r, int x, int p) {
    if (l == r) {
        mi[i] = mx[i] = p;
        return;
    }
    if (x <= mid) {
        update(lson, x, p);
    } else {
        update(rson, x, p);
    }
    pushup(i);
}
pair<int, int> query(int i, int l, int r, int L, int R) {
    if (r < L or l > R) return make_pair(INT_MAX, INT_MIN);
    if (L <= l and r <= R) return make_pair(mi[i], mx[i]);
    const pair<int, int> a = query(lson, L, R);
    const pair<int, int> b = query(rson, L, R);
    return make_pair(min(a.first, b.first), max(a.second, b.second));
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, q;
    cin >> n >> q;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    build(1, 1, n);
    for (int i = 0; i < q; i++) {
        int op, l, r;
        cin >> op >> l >> r;
        if (op == 1) {
            update(1, 1, n, l, r);
        } else {
            const pair<int, int> q = query(1, 1, n, l, r);
            cout << (q.second - q.first == r - l ? "YES" : "NO") << "\n";
        }
    }
    return 0;
}

E - 使徒袭来

题解

基本不等式:

$\frac{a_1 + a_2 + \dots + a_n}{n} \ge \sqrt[n]{a_1 \times a_2 \times \dots \times a_n}$

本题为:

$a_1 + a_2 + a_3 \ge 3 \times \sqrt[3]{a_1 \times a_2 \times a_3}$

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n;
    cin >> n;
    printf("%.3f\n", 3.0 * pow(n, 1.0 / 3.0));
    return 0;
}

F - 核弹剑仙

题解一

根据武器的破坏力由弱到强单向建图,枚举出发点,被访问的点的个数即为破坏力强于出发点的武器个数。

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m;
    cin >> n >> m;
    vector<vector<int>> G(n);
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        --u, --v;
        G[v].push_back(u);
    }
    vector<bool> vis(n);
    function<void(int)> dfs = [&](int u) {
        if (vis[u]) return;
        vis[u] = true;
        for (auto v : G[u]) {
            dfs(v);
        }
    };
    for (int i = 0; i < n; i++) {
        fill(vis.begin(), vis.end(), false);
        dfs(i);
        cout << count(vis.begin(), vis.end(), true) - 1 << "\n";
    }
    return 0;
}

题解二

根据武器的破坏力由强到弱单向建图拓扑排序,并用 $bitset$ 的状态来记录每个之前遍历到的武器,比当前武器破坏力强的武器个数即为  dp[i].count()  。

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m;
    cin >> n >> m;
    vector<vector<int>> G(n);
    vector<int> deg(n);
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        --u, --v;
        G[u].push_back(v);
        ++deg[v];
    }
    vector<bitset<1005>> dp(n);
    queue<int> que;
    for (int i = 0; i < n; i++) {
        if (deg[i] == 0)
            que.push(i);
    }
    while (!que.empty()) {
        int u = que.front();
        que.pop();
        for (auto v : G[u]) {
            dp[v] |= dp[u];
            dp[v].set(u);
            if (--deg[v] == 0)
                que.push(v);
        }
    }
    for (int i = 0; i < n; i++) 
        cout << dp[i].count() << " \n"[i == n - 1];
    return 0;
}

G - 虚空之力

题解一

模拟。

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    int a = 0, b = 0, c = 0, d = 0;
    for (int i = 0; i < n; i++) {
        char x;
        cin >> x;
        if (x == 'k') ++a;
        if (x == 'i') ++b;
        if (x == 'n') ++c;
        if (x == 'g') ++d;
    }
    int ans = 0;
    while (a >= 1 and b >= 2 and c >= 2 and d >= 2) {
        a -= 1;
        b -= 2;
        c -= 2;
        d -= 2;
        ans += 2;
    }
    while (a >= 1 and b >= 1 and c >= 1 and d >= 1) {
        a -= 1;;
        b -= 1;
        c -= 1;
        d -= 1;
        ans += 1;
    }
    cout << ans << "\n";
    return 0;
}

题解二

数学。

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    int a = 0, b = 0, c = 0, d = 0;
    for (int i = 0; i < n; i++) {
        char x;
        cin >> x;
        if (x == 'k') ++a;
        if (x == 'i') ++b;
        if (x == 'n') ++c;
        if (x == 'g') ++d;
    }
    int mi = min({a, b / 2, c / 2, d / 2});
    cout << 2 * mi + min({a - mi, b - 2 * mi, c - 2 * mi, d - 2 * mi}) << "\n";
    return 0;
}

H - 社团游戏

题解

二维前缀和 + 二分。

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m, k;
    cin >> n >> m >> k;
    vector<vector<char>> MP(n + 1, vector<char>(m + 1));
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            cin >> MP[i][j];
    vector<vector<vector<int>>> dp(n + 1, vector<vector<int>>(m + 1, vector<int>(26)));
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            for (int k = 0; k < 26; k++) {
                dp[i][j][k] = dp[i - 1][j][k] + dp[i][j - 1][k] - dp[i - 1][j - 1][k] + (MP[i][j] == 'a' + k);
            }
        }
    }
    function<bool(int, int, int)> ok = [&](int x, int y, int d) {
        int nx = x + d - 1, ny = y + d - 1;
        for (int i = 0; i < 26; i++) {
            if (dp[nx][ny][i] - dp[x - 1][ny][i] - dp[nx][y - 1][i] + dp[x - 1][y - 1][i] > k) {
                return false;
            }
        }
        return true;
    };
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            int l = 1, r = min(n - i + 1, m - j + 1);
            while (r - l > 1) {
                int mid = (l + r) / 2;
                if (ok(i, j, mid)) {
                    l = mid;
                } else {
                    r = mid - 1;
                }
            }
            cout << (ok(i, j, r) ? r : l) << " \n"[j == m];
        }
    }
    return 0;
}

I - 名作之壁

题解

尺取 + 单调队列。

代码

#include <bits/stdc++.h>
using namespace std;
constexpr int MOD = 1e9;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, k, x, b, c;
    cin >> n >> k >> x >> b >> c;
    vector<int> a(n + 1);
    a[0] = x;
    long long ans = 0;
    deque<int> mi, mx;
    for (int l = 1, r = 1; r <= n; r++) {
        a[r] = (1LL * a[r - 1] * b + c) % MOD;
        while (mi.size() and a[mi.back()] >= a[r]) mi.pop_back();
        mi.push_back(r);
        while (mx.size() and a[mx.back()] <= a[r]) mx.pop_back();
        mx.push_back(r);
        while (a[mx.front()] - a[mi.front()] > k) {
            ans += n - r + 1;
            ++l;
            if (mi.front() < l) mi.pop_front();
            if (mx.front() < l) mx.pop_front();
        }
    }
    cout << ans << "\n";
    return 0;
}

J - 逃跑路线

题解

$(2^1 - 1)\  \& \  (2^2 - 1)\  \& \  \dots \  \& \  (2^n - 1) = 1$

即判断最后横坐标的奇偶性。

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    int ans = 0;
    for (int i = 0; i < n; i++) {
        string s;
        cin >> s;
        ans += (s.back() - '0') & 1;
    }
    cout << (ans & 1) << "\n";
    return 0;
}

 

posted @ 2020-09-23 18:00  Kanoon  阅读(169)  评论(0编辑  收藏  举报