2023.7.5 NOIP模拟赛
本次集训的第一场模拟赛
大概先把前四题都看了眼 头疼 一道都不会
发现 T2 是上次来 HL 叉哥出的模拟的一道 T6 原题 然而我不会 我谢罪 我反思
还是先看 T1
首先暴力统计
然后发现 60pts 的部分分可以暴力统计
问题在于松弛操作 首先我们要取
当然因为松弛是判更大 所以每次取出来的显然也是
然后在思考剩下
但是如果不是最小点的话 统计的时候就要把它连接的几个部分中已经统计过答案的点断开 不会维护这个 所以溜了
然后是 T2 首先看了下
然后是
顺着这个思路考虑 这题的特殊部分在于钦定的点只有一个 所以每次分成两个小子树只会有一边有限制
然后显然在这个钦定点到根节点的路径上所有数都要小于钦定的这个数 所以每次给它分配不小于它到根节点路径的大于它的数就行 但是我不会维护 所以寄了
但是 T2 还是稳拿的 所以先去看下 T3 T4
那俩比较复杂 但是看了下 T3 的每个数最多出现两次和 T4 链的部分分可能能拿下
先去看的 T4 结果样例没看懂 根本没法推 苦战一会还是无果
T3 不想看了 准备写 T2
首先先把组合数那个玩意写了 写了个看起来很对的记搜 但是没有一个样例是满足这个特殊性质的!!!就离谱
然后手动推了一下 (虽然它只有 8 种情况) 但是想了一下因为我还要写爆搜的那 20 分 到时候回来拍一下就行了
然后我就去写爆搜了
的部分分显然白给 打个 数组再判和 位置的这个数的大小关系即可 先不写
然后我就把这玩意忘了
然后脑子一短路 写了一个有它转移到两个儿子的爆搜 然后想到可能要枚举排列 还不好判是否搜到头
然后东搞搞西搞搞还是没搞明白 最后断点调试破防了
因为我回溯的搜并且是两个并列结构根本没法搜满
寄
然后不仅这
那就交吧 但愿那 20 分不会挂
然后考试还剩 20 分钟左右 又看了看 T4 是真没看明白啊
然后去看 T3 那个特殊性质 看一会就有思路了
然后就去写 发现是
但是发现有 60 的
然后又假了 发现这个玩意没了特殊性质是撑死
但是
然后就坐着开始云 把每个点对都抽象成
差不多了 然后考试结束了
预计:60 + 20 + 0 + 0
实际:60 + 20 + 0 + 0 rk14
很幸运的是我没拍的 20 分没挂 但实在没啥好高兴的
幸好这不是 NOIP 不然我原地退役得了
然后看了一下大家 T2 有 20 分的基本都是写的爆搜的 20 就我一个是那个特殊性质的 20
中午一交流就想起来最开始那个对的搜法了 难绷
A.道路负载
实际上赛时树的那个思路已经很接近了
个人比较喜欢的一个思路是把点的贡献转化到边上来 即两边点权取
然后因为如果一个边连接两个连通块 它必须是小于任何一个在连通块内部的边 此时它的贡献就是边权 * 连通块 1 的大小 * 连通块 2 的大小
然后因为要求这个边是图里最小的嘛 所以从大到小一个个加边 并查集维护即可
当然直接用点做也是可行的
code:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e5 + 0721;
int c[N], fa[N], siz[N];
int n, m;
ll ans;
struct node {
int u, v, w;
friend bool operator<(node x, node y) {
return x.w > y.w;
}
} edge[N];
void init() {
for (int i = 1; i <= n; ++i) fa[i] = i;
for (int i = 1; i <= n; ++i) siz[i] = 1;
}
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
int main() {
// freopen("data2.txt", "r", stdin);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) scanf("%d", &c[i]);
for (int i = 1; i <= m; ++i) {
scanf("%d%d", &edge[i].u, &edge[i].v);
edge[i].w = min(c[edge[i].u], c[edge[i].v]);
}
sort(edge + 1, edge + 1 + m);
init();
for (int i = 1; i <= m; ++i) {
int fu = find(edge[i].u), fv = find(edge[i].v);
if (fu == fv) continue;
// cout << siz[fu] * siz[fv] * edge[i].w << endl;
ans += 1ll * siz[fu] * siz[fv] * edge[i].w;
fa[fu] = fv;
siz[fv] += siz[fu];
siz[fu] = 0;
}
printf("%lld", ans);
return 0;
}
B.小根堆
叉哥精选
同样是基于赛时那个思路
接着考虑 我们设
那么就有
那么答案即为
我们考虑从
考虑
对于有
对于另一个子树(即
这俩乘起来就是
发现前面是个后缀和
那总复杂度就是
有个小优化:因为
code:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod = 1e9 + 7;
const int N = 1e5 + 0721;
ll fac[N], inv[N], f[N], g[N], h[N];
int siz[N];
int n, loc, num;
void init() {
fac[0] = fac[1] = 1;
inv[0] = inv[1] = 1;
for (int i = 2; i <= n; ++i) {
fac[i] = fac[i - 1] * i % mod;
inv[i] = ((mod - mod / i * inv[mod % i]) % mod + mod) % mod;
}
for (int i = 2; i <= n; ++i) inv[i] = inv[i - 1] * inv[i] % mod;
}
int C(int x, int y) {
if (y < x) return 0;
return fac[y] * inv[x] % mod * inv[y - x] % mod;
}
void dfs(int x) {
siz[x] = 1;
int ls = (x << 1), rs = (x << 1 | 1);
if (ls <= n) {
dfs(ls);
siz[x] += siz[ls];
}
if (rs <= n) {
dfs(rs);
siz[x] += siz[rs];
}
return;
}
ll dp(int x) {
if (f[x] != -1) return f[x];
if (siz[x] < 3) return 1;
ll ans = 1;
int ls = (x << 1), rs = (x << 1 | 1);
ans = 1ll * dp(ls) * dp(rs) % mod * C(siz[ls], siz[x] - 1) % mod;
return f[x] = ans;
}
void solve() {
init();
memset(f, -1, sizeof f);
dfs(1);
g[num] = dp(loc) * C(siz[loc] - 1, n - num);
while (loc != 1) {
int tmp = loc;
loc >>= 1;
for (int i = n; i >= 1; --i) {
h[i] = (h[i + 1] + g[i]) % mod;
}
for (int i = 1; i <= n; ++i) g[i] = h[i + 1] * dp(tmp ^ 1) % mod * C(siz[tmp ^ 1], n - i - siz[tmp]) % mod;
}
printf("%lld",g[1]);
}
int main() {
scanf("%d%d%d", &n, &loc, &num);
solve();
return 0;
}
C.模式匹配
设
首先有
但是显然有可能
显然
我们还是考虑确定一个找另一个 假如我们确定了
那么问题就变成了在 b 前面查找一个最大的
这个东西就可以用主席树压掉一维
首先我们第
这样我们查询的时候查询
然后我们查询
并且注意此题是允许
code:
#include <bits/stdc++.h>
#define mid (l + r >> 1)
using namespace std;
const int N = 2e7 + 0721;
const int M = 5e5 + 0721;
int a[M], lst[M], nxt[M], loc[M], T[M], f[M];
int n;
struct tree {
int tr[N], ls[N], rs[N];
int cnt = 0;
int build(int l, int r) {
int k = ++cnt;
if (l == r) return k;
ls[k] = build(l, mid);
rs[k] = build(mid + 1, r);
return k;
}
int modify(int pre, int l, int r, int loc, int x) {
int k = ++cnt;
tr[k] = tr[pre], ls[k] = ls[pre], rs[k] = rs[pre];
tr[k] = max(tr[k], x);
if (l == r) return k;
if (loc <= mid) ls[k] = modify(ls[pre], l, mid, loc, x);
else rs[k] = modify(rs[pre], mid + 1, r, loc, x);
return k;
}
int query(int k, int l, int r, int u, int v) {
if (u <= l && v >= r) {
return tr[k];
}
int ret = 0;
if (u <= mid) ret = max(ret, query(ls[k], l, mid, u, v));
if (v > mid) ret = max(ret, query(rs[k], mid + 1, r, u, v));
return ret;
}
} seg;
void solve() {
for (int i = 1; i <= n; ++i) {
lst[i] = loc[a[i]];
loc[a[i]] = i;
}
memset(loc, 0, sizeof loc);
for (int i = n; i >= 1; --i) {
nxt[i] = loc[a[i]];
loc[a[i]] = i;
}
// for (int i = 1; i <= n; ++i) cout << i << " " << lst[i] << " " << nxt[i] << endl;
T[0] = seg.build(1, n);
for (int i = 1; i <= n; ++i) {
if (nxt[i] == 0) T[i] = T[i - 1];
else T[i] = seg.modify(T[i - 1], 1, n, nxt[i], i);
}
// cerr << seg.query(T[n], 1, n, 1, n) << "\n";
for (int i = 1; i <= n; ++i) {
int tmp = lst[lst[lst[i]]];
if (tmp != 0) f[i] = f[tmp - 1] + 4;
int x = lst[i];
if (x != 0) {
tmp = seg.query(T[x - 1], 1, n, x + 1, i - 1);
// cout << i << " " << tmp << "\n";
if (tmp != 0) f[i] = max(f[i], f[tmp - 1] + 4);
}
f[i] = max(f[i], f[i - 1]);
}
printf("%d",f[n]);
}
int main() {
// freopen("ex_21.in", "r", stdin);
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
solve();
return 0;
}
D.随机游走
像极了我考试做题的状态
首先那个柿子是
其中
然后就需要用到高斯消元然后把
然后又是什么玩意优化的 因为我不会高斯消元 所以前面的区域以后再来探索吧
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!