2024.11.22 CW 模拟赛

题目 & 题解

T1

思路

考虑先将原序列的逆序对个数求出来, 因为 \(a_i \le 5000\), 直接用一个桶统计即可.

再考虑将序列 \(a\) 拓展 \(k\) 次以后, 对于 \(a_{i+n}\) , 在上一段序列中的逆序对个数可以统计出来,

同理拓展到 \(a_{i+kn}\) , 那么便可以 \(\mathcal{O}(n)\) 计算了.

记得开 long long !

#include "iostream"

using namespace std;

#define int long long

const int N = 5e3 + 10;

int n, k;
int a[N], t[N];
int tot = 0, cnt[N];

inline void init()
{
    cin >> n >> k;
    for (int i = 1; i <= n; ++i)
    {
        cin >> a[i], ++t[a[i]];
        for (int j = 5e3; j ^ a[i]; --j)
            tot += t[j];
    }
    tot *= k;

    for (int i = 1; i <= n; ++i)
        for (int j = 5e3; j ^ a[i]; --j)
            cnt[i] += t[j];
    return;
}

inline void calculate()
{
    if (k == 1)
    {
        cout << tot << '\n';
        return;
    }

    for (int i = 1; i <= n; ++i)
        tot += (k * (k - 1) / 2) * cnt[i];

    cout << tot << '\n';
    return;
}

inline void solve()
{
    init();
    calculate();
    return;
}

signed main()
{
    cin.tie(nullptr)->ios::sync_with_stdio(false);
    solve();
    return 0;
}

T2

算法

最小生成树, 贪心.

思路

举个例子, 假设有两个坐标 \((x_1,y_1)\), \((x_2,y_2)\), 如果两点之间的欧氏距离的平方大于 \(v\), 那么通过空降的花费一定小于通信员的花费.

所以, 我们只在欧氏距离的平方小于 \(v\) 的点之间连边, 再在图上跑最小生成树即可(寻求最小的使其连通的花费).

用 Kruskal 的复杂度是 \(\mathcal{O}(n^2 \log n^2)\), 实测可以通过.

不过最好还是写 Prim.

#include "iostream"
#include "algorithm"

using namespace std;

template <typename T>
inline void read(T &x)
{
    x = 0;
    char ch = getchar();
    while (ch < '0' or ch > '9')
        ch = getchar();
    while (ch >= '0' and ch <= '9')
        x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
    return;
}

const int N = 5e3 + 1, M = 2.5e7 + 1;

int n, V;
int x[N], y[N];
int fa[N];

struct Edge
{
    int u, v, w;
    friend inline bool operator<(Edge x, Edge y)
    {
        return x.w < y.w;
    }
} e[M];
int cnt = 0;
inline void add(int u, int v, int w)
{
    e[++cnt] = {u, v, w};
    return;
}

inline int dis(int i, int j)
{
    return (x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]);
}

inline void init()
{
    read(n), read(V);
    for (int i = 1; i <= n; ++i)
        read(x[i]), read(y[i]), fa[i] = i;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j ^ i; ++j)
        {
            int d = dis(i, j);
            if (d < V)
                add(i, j, d);
        }
    return;
}

int tot = 0;
long long ans = 0;

int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }

inline void Kruskal()
{
    sort(e + 1, e + cnt + 1);
    for (int i = 1; i <= cnt; ++i)
    {
        if (tot == n - 1)
            break;
        int f1 = find(e[i].u), f2 = find(e[i].v);
        if (f1 ^ f2)
        {
            ++tot;
            ans += e[i].w;
            fa[f1] = f2;
        }
    }
    return;
}

inline void calculate()
{
    Kruskal();
    printf("%lld", ans + (n - tot) * V);
    return;
}

inline void solve()
{
    init();
    calculate();
    return;
}

int main()
{
    solve();
    return 0;
}

T3

原题(AGC007D)

算法

动态规划及其优化.

思路

模拟一下样例, 可以发现 \([0,E]\) 中每一个点要么被经过 1 次, 要么被经过 3 次.

我们想象一下操作的过程, 首先连续往右走一段给熊喂糖果, 然后等到一个合适的时机,回头把硬币捡起来, 再往右走, 然后一直重复这个动作.

所以我们可以令 \(f_i\) 表示到 \(a_i\) 这个位置时, 当前面所有金币都已经收集完成的最小时间.

则不难有以下转移方程:

\[f_i = \min_{j<i} (f_j + a_i - a_j + \max (T,2×(a_i - a_{j+1}))) \]

因为 \(f_i\) 是从 \(f_j\ (j<i)\) 转移而来, 所以其中所有的 \(a_i-a_j\) 均会被抵消, 只剩下 \(a_n\) , 也就是 \(E\).

将其提出, 就有:

\[f_i = \min_{j<i} (f_j + \max (T,2×(a_i - a_{j+1}))) \]

如果暴力转移的话是 \(\mathcal{O}(n^2)\) 的, 不足以通过此题, 考虑优化.

分类讨论一下:

  • \(T < 2×(a_i-a_{j+1})\)
    \(f_i = \min (f_j + 2×(a_i - a_{j+1}))\).
  • \(T \ge 2×(a_i-a_{j+1})\)
    \(f_i = \min (f_j + T)\).

因为 \(a_i-a_{j+1}\)\(i\) 增大而增大,

所以可以维护一个队列, 满足条件①的留在队伍里, 每次队首转移, 所有出队的 \(j\) 都满足条件②, 记录一下它们的最小值, 做到了 \(\mathcal{O}(n)\) 的复杂度.

#include "iostream"
#include "queue"

using namespace std;

const int N = 1e5 + 1;

int n, e, t;
int a[N];
long long f[N];

inline void init()
{
    scanf("%d %d %d", &n, &e, &t);
    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i), f[i] = 1e18;
    f[0] = 0;
    return;
}

queue<int> q;

inline void calculate()
{
    long long mn = 1e18;
    q.push(0);
    for (int i = 1; i <= n; ++i)
    {
        while (!q.empty() and (a[i] - a[q.front() + 1] << 1ll) > t)
            mn = min(mn, f[q.front()] - (a[q.front() + 1] << 1ll)), q.pop();
        f[i] = min(f[i], f[q.front()] + t);
        f[i] = min(f[i], mn + (a[i] << 1ll));
        q.push(i);
    }
    printf("%lld", f[n] + e);
    return;
}

inline void solve()
{
    init();
    calculate();
    return;
}

int main()
{
    solve();
    return 0;
}
posted @   Steven1013  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示