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
算法
动态规划及其优化.
思路
模拟一下样例, 可以发现 \([0,E]\) 中每一个点要么被经过 1 次, 要么被经过 3 次.
我们想象一下操作的过程, 首先连续往右走一段给熊喂糖果, 然后等到一个合适的时机,回头把硬币捡起来, 再往右走, 然后一直重复这个动作.
所以我们可以令 \(f_i\) 表示到 \(a_i\) 这个位置时, 当前面所有金币都已经收集完成的最小时间.
则不难有以下转移方程:
因为 \(f_i\) 是从 \(f_j\ (j<i)\) 转移而来, 所以其中所有的 \(a_i-a_j\) 均会被抵消, 只剩下 \(a_n\) , 也就是 \(E\).
将其提出, 就有:
如果暴力转移的话是 \(\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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现