2023牛客寒假演算法基礎集訓營1 ACDEFGHKLM

空白菌發表於2023-01-17

比賽連結

A

題解

知識點:模擬。

顯然。

(用char輸入到一半直接給答案跳出,WA了兩小時,無話可說。

時間複雜度 \(O(1)\)

空間複雜度 \(O(1)\)

程式碼

#include <bits/stdc++.h>
#define ll long long

using namespace std;

bool solve() {
    string s;
    cin >> s;
    vector<int> sum(2);
    for (int i = 1;i <= 10;i++) {
        if (s[i - 1] == '1') sum[!(i & 1)]++;
        if (sum[0] > sum[1] + (10 - i + 1) / 2 || sum[1] > sum[0] + (10 - i) / 2) {
            cout << i << '\n';
            return true;
        }
    }
    return false;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

C

題解

知識點:貪心。

顯然只要不是 \(0\) 的論文只要不動就會有 \(1\) 的貢獻,動了也至多有 \(1\) 的貢獻不如不動。因此,記錄非 \(0\) 論文數量即可。

時間複雜度 \(O(n)\)

空間複雜度 \(O(n)\)

程式碼

#include <bits/stdc++.h>
#define ll long long

using namespace std;

int a[100007];
bool solve() {
    int n;
    cin >> n;
    for (int i = 1;i <= n;i++) cin >> a[i];
    int ans = 0;
    for (int i = 1;i <= n;i++) ans += a[i] != 0;
    cout << ans << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

D

題解

知識點:計算幾何,貪心。

\((p_x,p_y)\) 會有四個位置,矩陣內、上方、右方、右上方,分別進行討論:

  1. \(p_x \leq x,p_y \leq y\) 在矩陣內部,比值最大值為 \(\dfrac{\max(px,x-px)\cdot \max(py,y-py)}{xy}\)
  2. \(p_x > x,p_y < y\) 在矩陣右方,比值最大值為 \(\max\left(\dfrac{xp_y}{xy+(p_x-x)p_y},\dfrac{x(y-p_y)}{xy+(p_x-x)(y-p_y)}\right)\)
  3. \(p_x < x,p_y > y\) 在矩陣上方,比值最大值為 \(\max\left(\dfrac{p_xy}{xy+p_x(p_y-y)},\dfrac{(x-p_x)y}{xy+(x-p_x)(p_y-y)}\right)\)
  4. \(p_x \geq x,p_y \geq y\) 在矩陣右上方,比值最大值為 \(\dfrac{xy}{p_xp_y}\) ,這個需要肉眼觀察法qwq。

(手殘把 \(y\) 寫成 \(x\) ,又瞪了半小時。。。

時間複雜度 \(O(1)\)

空間複雜度 \(O(1)\)

程式碼

#include <bits/stdc++.h>
#define ll long long

using namespace std;

bool solve() {
    int x, y, px, py;
    cin >> x >> y >> px >> py;

    if (px <= x && py <= y) {
        cout << fixed << setprecision(10) << 1.0 * max(px, x - px) * max(py, y - py) / x / y << '\n';
    }
    else if (px > x && py < y) {
        double a = 1.0 * x * py / (x * y + py * (px - x));
        double b = 1.0 * x * (y - py) / (x * y + (y - py) * (px - x));
        cout << fixed << setprecision(10) << max(a, b) << '\n';
    }
    else if (py > y && px < x) {
        double a = 1.0 * y * px / (x * y + px * (py - y));
        double b = 1.0 * y * (x - px) / (x * y + (x - px) * (py - y));
        cout << fixed << setprecision(10) << max(a, b) << '\n';
    }
    else if (px >= x && py >= y) {
        cout << fixed << setprecision(10) << 1.0 * x * y / px / py << '\n';
    }
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

E

題解

知識點:計算幾何。

前兩種操作是平移和旋轉,並不會改變兩條邊的相對位置關係,而第三種操作可以透過映象翻折改變位置關係。

先確定 \(ABC\)\(DEF\) 靠左的一條邊,然後比較長度,如果不同則一定用過第三種操作。

時間複雜度 \(O(1)\)

空間複雜度 \(O(1)\)

程式碼

#include <bits/stdc++.h>
using ll = long long;
using namespace std;

using ld = long double;
const ld pi = acos(-1.0L);
const ld eps = 1e-8;
template<class T>
struct Point {
    T x, y;
    Point(const T &x = 0, const T &y = 0): x(x), y(y) {}
    Point(const Point &A, const Point &B): x(B.x - A.x), y(B.y - A.y) {}

    Point &operator+=(const Point &P) { x += P.x, y += P.y;return *this; }
    Point &operator-=(const Point &P) { x -= P.x, y -= P.y;return *this; }
    Point &operator*=(const T &k) { x *= k, y *= k;return *this; }
    Point &operator/=(const T &k) { x /= k, y /= k;return *this; }
    friend Point operator-(const Point &P) { return Point(-P.x, -P.y); }
    friend Point operator+(Point A, const Point &B) { return A += B; }
    friend Point operator-(Point A, const Point &B) { return A -= B; }
    friend Point operator*(Point P, const T &k) { return P *= k; }
    friend Point operator/(Point P, const T &k) { return P /= k; }

    bool operator==(const Point &P) const { return (abs(x - P.x) <= eps && abs(y - P.y) <= eps); }

    //點乘
    friend T operator*(const Point &A, const Point &B) { return A.x * B.x + A.y * B.y; }
    //叉乘
    friend T operator^(const Point &A, const Point &B) { return A.x * B.y - A.y * B.x; }

    //P的相對方向,逆時針為1
    int toLeft(const Point &P) const {
        T cross = (*this) ^ P;
        return (cross > eps) - (cross < -eps);
    }

    //向量長度的平方
    T len2() const { return (*this) * (*this); }
    //兩點距離的平方
    friend T dist2(const Point &A, const Point &B) { return (A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y); }

    //*一定包含浮點的函式
    //兩點距離
    friend ld dist(const Point &A, const Point &B) { return sqrt(dist2(A, B)); }
    //向量長度
    ld len()const { return sqrt(len2()); }
    //向量夾角
    friend ld ang(const Point &A, const Point &B) { return acos(max(-1.0L, min(1.0L, (A * B) / (A.len() * B.len())))); }
    //逆時針旋轉rad
    Point rot(const ld rad) const { return { x * cos(rad) - y * sin(rad), x * sin(rad) + y * cos(rad) }; }
    //逆時針旋轉引數
    Point rot(const ld cosr, const ld sinr) const { return { x * cosr - y * sinr, x * sinr + y * cosr }; }
};

bool solve() {
    ld xa, ya, xb, yb, xc, yc;
    cin >> xa >> ya >> xb >> yb >> xc >> yc;
    ld xd, yd, xe, ye, xf, yf;
    cin >> xd >> yd >> xe >> ye >> xf >> yf;

    Point<ld> BA({ xb,yb }, { xa,ya });
    Point<ld> BC({ xb,yb }, { xc,yc });
    Point<ld> ED({ xe,ye }, { xd,yd });
    Point<ld> EF({ xe,ye }, { xf,yf });

    if (BA.toLeft(BC) == 1) swap(BA, BC);
    if (ED.toLeft(EF) == 1) swap(ED, EF);

    if (abs(BA.len() - ED.len()) < eps) cout << "NO" << '\n';
    else cout << "YES" << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

F

題解

知識點:並查集。

注意到,即便一個點放完炸彈不能走,但因為我們能控制放炸彈的順序,炸彈是影響不了我們的路線的。所以實際上,只要炸彈點是連通的,我們就一定可以從這個連通塊的任何點開始走,並且以任何點結束,答案便是這個連通塊大小的平方。

當然如果炸彈點不在一個連通塊就無解。

或者,沒有炸彈點,我們就可以把所有連通塊大小平方加起來。

用並查集維護連通性,以及連通塊大小。

時間複雜度 \(O((m+n) \log n)\)

空間複雜度 \(O(n)\)

程式碼

#include <bits/stdc++.h>
using ll = long long;
using namespace std;

struct DSU {
    vector<int> fa;
    vector<int> w;

    explicit DSU(int n):fa(n + 1), w(n + 1, 1) {
        for (int i = 1;i <= n;i++)
            fa[i] = i;
    }

    void init(int n) {
        for (int i = 1;i <= n;i++)
            fa[i] = i, w[i] = 1;
    }

    int find(int x) {
        if (fa[x] == x) return x;
        fa[x] = find(fa[x]);
        return fa[x];
    }//按需修改

    bool same(int x, int y) { return find(x) == find(y); }

    bool merge(int x, int y) {
        if (same(x, y)) return false;
        int px = fa[x], py = fa[y];
        fa[px] = py;
        w[py] += w[px];
        return true;
    }//按需修改;注意方向,x合併到y
};

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;
    DSU dsu(n);
    for (int i = 1;i <= m;i++) {
        int u, v;
        cin >> u >> v;
        dsu.merge(u, v);
    }

    vector<int> c(n + 1);
    for (int i = 1;i <= n;i++) cin >> c[i];

    bool ok = 1;
    int rt = 0;
    for (int i = 1;i <= n;i++) {
        if (c[i]) {
            if (rt) ok &= dsu.same(i, rt);
            else rt = dsu.find(i);
        }
    }
    if (!rt) {
        ll ans = 0;
        for (int i = 1;i <= n;i++) {
            if (i == dsu.find(i)) ans += 1LL * dsu.w[i] * dsu.w[i];
        }
        cout << ans << '\n';
    }
    else if (ok) cout << 1LL * dsu.w[rt] * dsu.w[rt] << '\n';
    else cout << 0 << '\n';
    return 0;
}

G

題解

知識點:線段樹,數學。

眾所周知,分數是不會一直漲的qwq,所以 \(x_{i+1} = round(10\sqrt {x_i})\)\(x = 0,99,100\) 時會不變,這些點稱為不動點。同時,其他點也會快速收斂到 \(99\) 或者 \(100\) ,也就是說每個點最多修改幾次就不用改了。

因此,可以寫一個偽區間修改的線段樹,即單點修改但套一個區間修改的殼子。然後再加一個區間值都為 \(99,100\) 時的最佳化,可以用最大值和最小值夾逼實現。單點修改時也是同樣的,修改到 \(99,100\) 就可以結束了。

特判 \(a_i = 0\) 的情況,為了判斷統一可以把這個節點的 \(max\)\(min\) 的資訊修改為 \(100\) ,而 \(val\) 還是 \(0\) ,不影響求和。

因為每個點最多修改幾次可以看作常數,所以對單點修改次數是 $O(n) $ ,所以複雜度也是 \(O(n\log n)\)

時間複雜度 \(O(n \log n)\)

空間複雜度 \(O(n)\)

程式碼

#include <bits/stdc++.h>
using ll = long long;
using namespace std;

struct T {
    ll val;
    int mi, mx;
    static T e() { return T{ 0,0x3f3f3f3f,0 }; }
    friend T operator+(const T &a, const T &b) { return { a.val + b.val,min(a.mi,b.mi),max(a.mx,b.mx) }; }
};
///節點元封裝類,定義單位元"e"、合併"+"
struct F {
    int k;
    T operator()(const T &x) {
        int val = x.val;
        for (int i = 1;i <= k && val != 99 && val != 100;i++) {
            val = sqrt(val) * 10 + 0.5;
        }
        return T{ val,val,val };
    }
};
///修改元封裝類,定義對映"()"
class SegmentTree {
    const int n;
    vector<T> node;
    void update(int rt, int l, int r, int L, int R, F f) {
        if (node[rt].mi >= 99 && node[rt].mx <= 100) return;
        if (r < L || l > R) return;
        if (l == r) {
            node[rt] = f(node[rt]);
            return;
        }
        int mid = l + r >> 1;
        update(rt << 1, l, mid, L, R, f);
        update(rt << 1 | 1, mid + 1, r, L, R, f);
        node[rt] = node[rt << 1] + node[rt << 1 | 1];
    }
    T query(int rt, int l, int r, int x, int y) {
        if (l > y || r < x) return T::e();
        if (x <= l && r <= y) return node[rt];
        int mid = l + r >> 1;
        return query(rt << 1, l, mid, x, y) + query(rt << 1 | 1, mid + 1, r, x, y);
    }

public:
    SegmentTree(int _n):n(_n), node(_n << 2, T::e()) {}
    SegmentTree(int _n, vector<T> &src):n(_n), node(_n << 2, T::e()) {
        function<void(int, int, int)> build = [&](int rt, int l, int r) {
            if (l == r) {
                node[rt] = src[l];
                return;
            }
            int mid = l + r >> 1;
            build(rt << 1, l, mid);
            build(rt << 1 | 1, mid + 1, r);
            node[rt] = node[rt << 1] + node[rt << 1 | 1];
        };
        build(1, 1, n);
    }

    void update(int L, int R, F f) {
        update(1, 1, n, L, R, f);
    }

    T query(int x, int y) {
        return query(1, 1, n, x, y);
    }
};
///線段樹,建樹O(nlogn)、修改查詢O(logn),單點修改、區間查詢

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;
    vector<T> src(n + 1);
    for (int i = 1;i <= n;i++) {
        int x;
        cin >> x;
        if (x) src[i] = { x,x,x };
        else src[i] = { 0,100,100 };
    }
    SegmentTree sgt(n, src);
    while (m--) {
        int op;
        cin >> op;
        if (op == 1) {
            int l, r, k;
            cin >> l >> r >> k;
            sgt.update(l, r, { k });
        }
        else {
            cout << sgt.query(1, n).val << '\n';
        }
    }
    return 0;
}

H

題解

知識點:數學。

因為面積總和不變,記錄 \(n^2-1\) 多了或少了幾個半圓,一定會出現在最後一塊上,直接算即可。

時間複雜度 \(O(n^2)\)

空間複雜度 \(O(1)\)

程式碼

#include <bits/stdc++.h>
#define ll long long

using namespace std;

bool solve() {
    int n;
    cin >> n;
    int ans = 10;
    for (int i = 1;i <= n * n - 1;i++) {
        for (int j = 1;j <= 4;j++) {
            char x;
            cin >> x;
            if (x == '1') ans++;
            else if (x == '2') ans--;
        }
    }
    cout << ans << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

K

題解

知識點:線性dp。

\(n\leq 2\) 特判為 \(0\)

\(dp[i][j][k]\) 表示為考慮了前 \(i\) 個位置,有 \(j\)\(1\) ,最後兩個位置的狀態是 \(k\)00,10,01,11 四種狀態)。

初值設定:

if (m >= 0) dp[3][0][0] = 0;
if (m >= 1) dp[3][1][0] = dp[3][1][1] = dp[3][1][2] = 0;
if (m >= 2) dp[3][2][1] = dp[3][2][2] = dp[3][2][3] = 1;
if (m >= 3) dp[3][3][3] = 1;

轉移方程:

dp[i][j][0] = min(dp[i - 1][j][0], dp[i - 1][j][1]);
dp[i][j][1] = min(dp[i - 1][j][2], dp[i - 1][j][3] + 1);
if (j >= 1) dp[i][j][2] = min(dp[i - 1][j - 1][0], dp[i - 1][j - 1][1] + 1);
if (j >= 2) dp[i][j][3] = min(dp[i - 1][j - 1][2] + 1, dp[i - 1][j - 1][3] + 1);

本題實際可以貪心 \(O(n)\) 過,加點推公式可以 \(O(1)\) 過。

(公式寫掛了,沒考慮填填滿前的幾個 \(1\) 的情況不符合規律要特判,於是用dp硬碾過去了qwq。

時間複雜度 \(O(nm)\)

空間複雜度 \(O(nm)\)

程式碼

#include <bits/stdc++.h>
#define ll long long

using namespace std;

int dp[1007][1007][4];
int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;
    if (n <= 2) {
        cout << 0 << '\n';
        return 0;
    }
    memset(dp, 0x3f3f3f3f, sizeof dp);
    if (m >= 0) dp[3][0][0] = 0;
    if (m >= 1) dp[3][1][0] = dp[3][1][1] = dp[3][1][2] = 0;
    if (m >= 2) dp[3][2][1] = dp[3][2][2] = dp[3][2][3] = 1;
    if (m >= 3) dp[3][3][3] = 1;
    for (int i = 4;i <= n;i++) {
        for (int j = 0;j <= min(i, m);j++) {
            dp[i][j][0] = min(dp[i - 1][j][0], dp[i - 1][j][1]);
            dp[i][j][1] = min(dp[i - 1][j][2], dp[i - 1][j][3] + 1);
            if (j >= 1) dp[i][j][2] = min(dp[i - 1][j - 1][0], dp[i - 1][j - 1][1] + 1);
            if (j >= 2) dp[i][j][3] = min(dp[i - 1][j - 1][2] + 1, dp[i - 1][j - 1][3] + 1);
        }
    }
    cout << min({ dp[n][m][0],dp[n][m][1],dp[n][m][2],dp[n][m][3] }) << '\n';
    return 0;
}

L

題解

知識點:數學。

先團隊,再個人,選擇是獨立的可以分開來算加起來。

團隊選 \(1,2,3,4,5\) 次選到的機率都是 \(\dfrac{1}{5}\) ,特別地 \(5\) 才能選中次只選 \(4\) 次就可以結束了,期望 \(\dfrac{1+2+3+4+4}{5}\)

個人同理,期望為 \(\dfrac{1+2+3+3}{4}\)

總和為 \(5.05\) ,選 \(32\)

(傻不拉幾的真信了運氣題,題都沒看直接抽卡,抽了十幾發。。

時間複雜度 \(O(1)\)

空間複雜度 \(O(1)\)

程式碼

#include <bits/stdc++.h>
#define ll long long

using namespace std;

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

    cout << 32 << '\n';
    return 0;
}

M

題解

知識點:線性dp。

\(dp[i][j]\) 表示為考慮到倒數第 \(i\) 個人,還剩下 \(j\) 個仙貝。

初始狀態 \(dp[0][0] = 0\)

列舉給了倒數第 \(i\) 個人 \(k\) 個仙貝的情況轉移即可。

(傻不拉幾的真信了找規律題,找了1小時屁都沒發現。。。

時間複雜度 \(O(nm^2)\)

空間複雜度 \(O(nm)\)

程式碼

#include <bits/stdc++.h>
#define ll long long

using namespace std;

double dp[507][507];
int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;
    for (int i = 1;i <= n;i++) {
        for (int j = 0;j <= m;j++) {
            dp[i][j] = dp[i - 1][j];
            for (int k = 1;k <= j;k++) {
                dp[i][j] = max(dp[i][j], dp[i - 1][j - k] + 1.0 * k / j);
            }
        }
    }
    cout << fixed << setprecision(10) << dp[n][m] << '\n';
    return 0;
}

相關文章