HD 2025 春季联赛 1

好难啊好难啊

Log

3.15 增加 1008 的题解

1001

1002

概率 数学

解题思路

为了方便判断那两个人进行 duel,我们将所有人的编号都减一,即编号的范围是 \([0, N)\)

根据题意我们知道相邻的两个人(编号为 \(2k\)\(2k + 1\)) duel 后,胜利的那个人的编号就是 \(k\)。令 \(P(i)\) 为第 \(i\) 个人是坏人(将题目说的 “不想碰到的敌人” 改成 “坏人” 以便于描述)的概率,则 \(P(k) = {P(2k) + P(2k + 1) \over 2}\)。根据这一点我们模拟每一局就好了。需要特别注意的是最后一个人,因为有可能没人跟他 duel。

CODE
void solve() {
    int n = 0, k = 0;
    std::cin >> n >> k;
    // 前者是编号,后者是概率,初始的时候是 1
    std::vector p(k + 1, std::pair<int, i64>{ 0, 1ll });
    for (int i = 0; i <= k; i++) {
        std::cin >> p[i].first;
        p[i].first--;
    }
    // p[0] 是主角的信息
    std::sort(p.begin() + 1, p.end());
    i64 ans = 1;
    // 知道只剩主角一个人了才停止
    for (; p.size() > 1 ; n = n + 1 >> 1, p[0].first >>= 1) {
        if ((p[0].first ^ 1) < n) {
            // 看 p 里面有没有主角的对手
            auto it = std::lower_bound(p.begin() + 1, p.end(), std::pair<int, i64>{ p[0].first ^ 1, 0 });
            if (it != p.end() && (*it).first == (p[0].first ^ 1)) {
                // 不碰到的概率
                (ans *= Mod + 1ll - (*it).second) %= Mod;
                p.erase(it); // 主角必胜,直接杀掉
            }
        }
        for (int j = 1; j < p.size(); j++) {
            if (j + 1 < p.size() && (p[j].first ^ 1) == p[j + 1].first) {
                std::pair<int, i64> tmp = { p[j].first >> 1, (p[j].second + p[j + 1].second) * inv2 % Mod };
                // 将两人设置成一样的,后面去重就好了
                p[j] = p[j + 1] = tmp;
                j++;
            }
            else {
                // 特判最后一个人,没人跟他 duel,他的概率就不变
                if ((p[j].first ^ 1) < n) {
                    (p[j].second *= inv2) %= Mod;
                }
                p[j].first >>= 1;
            }
        }
        //
        p.erase(std::unique(p.begin() + 1, p.end()), p.end());
    }
    std::cout << ans << '\n';
    return;
}

1004

数据结构 二分

谴责出题人不说长度为 1 的区间也算海浪区间

解题思路

根据题目的描述,海浪区间满足以下条件之一:

  • 奇数位置的最高海浪 严格小于 偶数位置的最低海浪
  • 奇数位置的最低海浪 严格大于 偶数位置的最高海浪

于是我们就把所给的高度序列按照奇偶分成两个序列,去维护区间最大值和最小值(st 表)。

首先我们需要观察出一点:我们从小到大枚举每个数作为海浪的右端点,找它最小的左端点,我们会发现这个左端点是不降的。因为每次发现形成不了海浪肯定是中间一个地方出了问题,所以左端点移过出问题的地方就好了。

根据这点我们可以用双指针维护出每个右端点所对应的最小左端点。

然后考虑如何回答每个询问。在询问区间 \([L, R]\),我们仍然从小到枚举每个数作为海浪区间的右端点 \(r\)。在这个过程中,由于左端点 \(l\) 是不降的,左端点肯定依次经过了在询问区间外(\(l < L\))和在询问区间内 \(L \leq l\) 两个阶段。对于前一个阶段,我们找到最后一个 \(r\),使得 \(l < L\) 就好了。对于后一个阶段,直接取对应长度最长的 \(r\) 就好了(st 表)。

CODE
// 这个 st 表跟常规的 st 表是反着来的
class st {
private:
    std::vector<std::array<std::array<int, 2>, M>> a;
    std::array<int, 2> getval(std::array<int, 2> &u, std::array<int, 2> &v) {
        return { std::min(u[0], v[0]), std::max(u[1], v[1]) };
    }
public:
    st() {
        insert(0);
    }
    void insert(int val) {
        a.push_back(std::array<std::array<int, 2>, M>{});
        int R = a.size() - 1;
        a[R][0] = { val, val };
        for (int l = 1; l < M; l++) {
            if (R - (1 << l) >= 0) {
                a[R][l] = getval(a[R][l - 1], a[R - (1 << l - 1)][l - 1]);
            }
        }
    }

    std::array<int, 2> ask(int l, int r) {
        if (l > r) {
            return { -1, -1 };
        }
        int len = log2(r - l + 1);
        return getval(a[l + (1 << len) - 1][len], a[r][len]);
    }
};

int main () {
    IOS

    int _t = 1;
    std::cin >> _t;
    while (_t--) {
        int n = 0, q = 0;
        std::cin >> n >> q;
        std::vector<i64> h(n + 1, 0);
        st ob, ev;
        for (int i = 1; i <= n; i++) {
            std::cin >> h[i];
            // 奇偶分组
            if (i & 1) {
                ob.insert(h[i]);
            }
            else {
                ev.insert(h[i]);
            }
        }
        std::vector L(n + 1, 0);
        // 维护区间内所有右端点对应海浪的最大长度
        st len;
        for (int l = 1, r = 1; r <= n; r++) {
            auto obb = ob.ask(l + 2 >> 1, r + 1 >> 1);
            auto eve = ev.ask(l + 1 >> 1, r >> 1);
            // 海浪区间满足区间中奇数位的最小值大于偶数位的最大值 或 奇数位的最大值小于偶数位的最小值
            while (l < r && obb[0] <= eve[1] && obb[1] >= eve[0]) {
                l++;
                obb = ob.ask(l + 2 >> 1, r + 1 >> 1);
                eve = ev.ask(l + 1 >> 1, r >> 1);
            }
            L[r] = l;
            len.insert(r - l + 1);
        }

        i64 ans = 0;
        for (int i = 1; i <= q; i++) {
            int l = 0, r = 0;
            std::cin >> l >> r;

            int ll = l, rr = r;
            while (ll <= rr) {
                int mid = ll + rr >> 1;

                if (L[mid] <= l) {
                    ll = mid + 1;
                }
                else {
                    rr = mid - 1;
                }
            }

            int le = std::max(rr - l + 1, len.ask(ll, r)[1]);
            // std::cout << le << '\n';
            (ans += 1ll * i * le) %= Mod;
        }
        std::cout << ans << '\n';
    }

    system("pause");

    return 0;
}

1005

dijkstra 分层图

解题思路

可以按分层图这么去想吧。

我们将原图分为 4 层,每一层代表一个方向。然后……就是纯 dijkstra 了

CODE
void solve()
{
    int n = 0, m = 0;
    std::cin >> n >> m;
    // t[i][j] 表示通过 (i ,j) 时的时间, D[i][j] 表示在 (i, j) 调转方向的时间
    std::vector t(n + 1, std::vector(m + 1, 0ll)), D(n + 1, std::vector(m + 1, 0ll));
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            std::cin >> t[i][j];
        }
    }
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            std::cin >> D[i][j];
        }
    }

    std::vector vis(n + 1, std::vector(m + 1, std::array<bool, 4>{ false, false, false, false }));
    // dis[i][j][k] 表示从起点到(i, j)的最短距离,k表示方向
    std::vector dis(n + 1, std::vector(m + 1, std::array<i64, 4>{ Inf, Inf, Inf, Inf }));

    std::priority_queue<std::tuple<i64, int, int, int>> q;
    q.push({ 0, 1, 0, 0});
    while (not q.empty()) {
        auto [d, x, y, k] = q.top();
        q.pop();
        if (vis[x][y][k]) {
            continue;
        }
        d = -d;
        vis[x][y][k] = true;
        dis[x][y][k] = d;

        for (int i = 0; i < 4; i++) {
            int xx = x + dx[i], yy = y + dy[i];
            if (xx < 1 || xx > n || yy < 1 || yy > m) {
                continue;
            }
            i64 nd = d + (i == k ? 0 : D[x][y]) + t[xx][yy];
            if (not vis[xx][yy][i] && dis[xx][yy][i] > nd) {
                dis[xx][yy][i] = nd;
                q.push({ -nd, xx, yy, i });
            }
        }
    }

    std::cout << std::min(dis[n][m][0] + D[n][m], dis[n][m][1]) << '\n';
}

1006

我愿称之为模拟

解题思路

6 种情况都试一遍,只保留非负整数解,然后判断哪个解在所有的方程里面都出现过。

CODE
void solve()
{
    int n = 0;
    std::cin >> n;
    std::unordered_map<int, int> mp;

    for (int i = 0; i < n; i++) {
        std::vector a(3, 0);
        for (int j = 0; j < 3; j++) {
            std::cin >> a[j];
        }
        std::unordered_set<int> s;
        for (int j = 0; j < 3; j++) {
            std::array<int, 2> v = { a[j], a[(j + 1) % 3] };
            int v3 = a[(j + 2) % 3];
            for (int k = 0; k < 2; k++) {
                if ((v[k] - v[1 - k]) % v3 == 0 && (v[k] - v[1 - k]) / v3 >= 0) {
                    s.insert((v[k] - v[1 - k]) / v3);
                }
            }
        }
        for (auto x : s) {
            mp[x]++;
        }
    }

    for (auto &[num, cnt] : mp) {
        if (cnt == n) {
            std::cout << num << '\n';
            return;
        }
    }

    return;
}

1007

博弈的小题

解题思路

当只有一个人时,我们肯定 1 块钱都不给他,直接独吞。

当有两个人的时候,第一位继承者无论如何都给你投反对票(分 0 块给他),所以我们一定要拿到第二个人的同意。如果你也给第二个人 0 块,那么对于第二个人,同意他得到 0 块不同意也得到 0 块,限额的海盗肯定给你票出去喂鱼了,所以给他 1 块,这样 他同意得到的就比他反对得到的多,因此肯定投同意。

当有 3 个人时,第一位继承者依旧反对,你依旧给他 0 块;此时我们要拉一个人才能保证方案通过,如果我拉第二个,我只用给他 1 块,因为分队最多也只有 0 块,但如果我拉第三个,我就要给他 2 块,应为他反对他就有一块了。

所以我们按照种思路,来拉拢只要 1 块钱的人给我们投同意,剩下的反正是要反对的给 0 块就好了。

于是规律就是 0101010101……

CODE
void solve()
{
    i64 n = 0;
    std::cin >> n;
    i64 ans = 0ll;
    if (n != 1) {
        n >>= 1;
        n <<= 1;
        ans = (2 + n) * (n >> 1) / 2ll;
    }
    std::cout << ans % Mod<< '\n';;
}

1008

计算几何(本题考查了有没有和出题人看过同一个视频

解题思路

给的结论是 (多边形最小圆覆盖的面积 - 多边形的面积) / 多边形最小圆覆盖的周长。

不太懂,就当做是学习了最小圆覆盖吧。

大致分为两步,求最小圆覆盖和求多边形面积。

CODE
constexpr int N = 1e5, MAX = 1e4;
const double EPS = 1e-7, PI = acos(-1.0);

using point = std::array<double, 2>;
std::array<i64, 2> b[N + 5];
point a[N + 5];

double dis2(point &u, point &v) {
    double x = u[0] - v[0], y = u[1] - v[1];
    return x * x + y * y;
}

point cen(point &u, point &v) {
    return { (u[0] + v[0]) / 2.0, (u[1] + v[1]) / 2.0 };
}
point cen(point &u, point &v, point &w) {
    auto &[x1, y1] = u;
    auto &[x2, y2] = v;
    auto &[x3, y3] = w;

    double A = x2 * x2 + y2 * y2 - x1 * x1 - y1 * y1, B = x3 * x3 + y3 * y3 - x1 * x1 - y1 * y1;  
    double x = (A * (y3 - y1) - B * (y2 - y1)) / (2.0 * (x2 - x1) * (y3 - y1) - 2.0 * (x3 - x1) * (y2 - y1));
    double y = (A * (x3 - x1) - B * (x2 - x1)) / (2.0 * (y2 - y1) * (x3 - x1) - 2.0 * (y3 - y1) * (x2 - x1));
    return { x, y };
}

void solve() {
    int n = 0;
    std::cin >> n;
    for (int i = 0; i < n; i++) {
        std::cin >> b[i][0] >> b[i][1];
        a[i][0] = b[i][0], a[i][1] = b[i][1];
    }

    // 求多边形面积
    double Sr = 0;
    for (int i = 0; i < n; i++) {
        int j = (i + 1) % n;
        Sr += (b[i][0] * b[j][1] - b[j][0] * b[i][1]);
    }
    Sr /= 2.0;

    // 求最小圆覆盖
    // 为了避免在计算距离过程中频繁开根,这里最后求出来的半径实际是半径的平方
    std::random_shuffle(a, a + n);
    point O = a[0];
    double r = 0;
    for (int i = 1; i < n; i++) {
        if (dis2(O, a[i]) - r <= EPS) {
            continue;
        }

        O = a[i], r = 0;
        
        for (int j = 0; j < i; j++) {
            if (dis2(O, a[j]) - r <= EPS) {
                continue;
            }
            
            O = cen(a[i], a[j]), r = dis2(O, a[j]);
            for (int k = 0; k < j; k++) {
                if (dis2(O, a[k]) - r <= EPS) {
                    continue;
                }
                
                O = cen(a[i], a[j], a[k]), r = dis2(O, a[i]);
            }
        }
    }
    
    std::cout << std::fixed << std::setprecision(6) << (PI * r - Sr) / (2.0 * PI * std::sqrt(r)) << '\n';
    return;
}

1009

dp 位运算

解题思路

我们考察 \(f(l, r)\) 的性质。我们令 \(g_r(l) = f(l, r)\) 为当 \(r\) 固定时,\(f(l, r)\)\(l\) 增加的函数。则可以发现 \(g_r(l)\) 是不增的,且最多只有 \(m \leq 20\) 个值,因为 \(g_r(l)\) 二进制下的值中 1 的数量肯定逐渐是减少的,且但凡减少了的 1 就不会恢复。

于是根据这点我们就可以对于每个 \(r\) 维护出值发生变化时的端点,然后 \(dp\) 就好了,dp 时取两个端所形成区的间最大值。

具体的实现看代码就好了。

CODE
// 实现了可以一边 push_back(即 insert 函数)一边查询区间最大值
class st {
private:
    std::vector<std::array<i64, M>> a;
public:
    st() {
        insert(0);
    }
    void insert(i64 val) {
        a.push_back(std::array<i64, M>{});
        int R = a.size() - 1;
        a[R][0] = val;
        for (int l = 1; l < M; l++) {
            if (R - (1 << l) + 1>= 0) {
                a[R][l] = std::max(a[R][l - 1], a[R - (1 << l - 1)][l - 1]);
            }
        }
    }

    i64 ask(int l, int r) {
        int len = log2(r - l + 1);
        return std::max(a[l + (1 << len) - 1][len], a[r][len]);
    }
};

void solve() {
    int n = 0, m = 0;
    std::cin >> n >> m;
    std::vector a(n + 1, 0);
    for (int i = 1; i <= n; i++) {
        std::cin >> a[i];
    }
    std::vector g(1 << m, 0ll);
    for (auto &i : g) {
        std::cin >> i;
    }

    // 对于每个位,维护上一个 0/1 出现在什么地方
    std::vector nxt(n + 1, std::vector(m, std::array<int, 2>{ 0, 0 }));
    for (int bit = 0; bit < m; bit++) {
        for (int i = 1; i <= n; i++) {
            int b = (a[i] >> bit) & 1;
            nxt[i][bit][b] = i; 
            nxt[i][bit][b ^ 1] = nxt[i - 1][bit][b ^ 1];
        }
    }

    // 以第 i 个数为右端点r,以第 jump[i][j][0] 个数为左端点l时,f(l, r) 第一次变到 jump[i][j][1]
    std::vector jump(n + 1, std::vector(m + 2, std::array<int, 2>{ 0, 0 }));
    std::priority_queue<int> q;
    for (int i = 1; i <= n; i++) {
        jump[i][0] = { i, 0 };
        // 看对于 a[i] 的每个位,看最近在哪里出现它的反。
        for (int j = 0; j < m; j++) {
            q.push(nxt[i][j][((a[i] >> j) & 1) ^ 1]);
        }
        int k = 1, res = 0;
        while (not q.empty()) {
            int cur = q.top();
            while (not q.empty() && q.top() == cur) {
                q.pop();
            }
            if (cur == 0) {
                break;
            }
            for (int j = 0; j < m; j++) {
                if (((a[i] ^ a[cur]) >> j) & 1) {
                    res |= (1 << j);
                }
            }
            jump[i][k++] = { cur, res };
        }
    }
    
    st dp;
    for (int i = 1; i <= n; i++) {
        i64 tmp = -Inf;
        int k = 0;
        while (k <= m && jump[i][k][0]) {
            auto &[r, val] = jump[i][k++];
            tmp = std::max(tmp, dp.ask(jump[i][k][0], r - 1) + g[val]);
        }
        dp.insert(tmp);
    }

    std::cout << dp.ask(n, n) << '\n';
    return;
}
posted @ 2025-03-10 23:07  Young_Cloud  阅读(30)  评论(0)    收藏  举报