[赛记] csp-s模拟2
不相邻集合 64pts
赛时打的用 $ set $ 打的假做法A了,但是没敢交,整了个暴力64pts;
可以发现,对于给定的一个序列,我们只需研究每个数一次就行,因为如果一个数出现多次,答案是不变的;
我们又可以发现,对于一个连续段(比如 1 2 3 4 5
,其答案最多为 $ \lceil \frac n2 \rceil $ ,其中 $ n $ 为此连续段的长度),所以我们只需动态维护极长连续段的长度即可;
用并查集可以很好维护,每次新加进来一个数的时候分类讨论即可;
时间复杂度:$ \Theta(n \alpha(n)) $;
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
int n;
int a[500005];
bool vis[500005];
int fa[500005], siz[500005];
int find(int x) {
if (x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
int w(int x, int y) {
if (x % y == 0) return x / y;
else return x / y + 1;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
int ma = -1;
for (int i = 1; i <= n; i++) {
cin >> a[i];
ma = max(ma, a[i]);
}
for (int i = 1; i <= ma; i++) {
fa[i] = i;
siz[i] = 1;
}
int ans = 1;
cout << 1 << ' ';
vis[a[1]] = true;
for (int i = 2; i <= n; i++) {
if (vis[a[i]]) {
cout << ans << ' ';
continue;
}
vis[a[i]] = true;
if (!vis[a[i] - 1] && !vis[a[i] + 1]) {
ans++;
}
if (!vis[a[i] - 1] && vis[a[i] + 1]) {
int x = find(a[i]);
int y = find(a[i] + 1);
ans -= w(siz[y], 2);
fa[x] = y;
siz[y] += siz[x];
ans += w(siz[y], 2);
}
if (vis[a[i] - 1] && !vis[a[i] + 1]) {
int x = find(a[i]);
int y = find(a[i] - 1);
ans -= w(siz[y], 2);
fa[x] = y;
siz[y] += siz[x];
ans += w(siz[y], 2);
}
if (vis[a[i] - 1] && vis[a[i] + 1]) {
int x = find(a[i]);
int y1 = find(a[i] - 1);
int y2 = find(a[i] + 1);
ans -= (w(siz[y1], 2) + w(siz[y2], 2));
fa[x] = y1;
siz[y1] += siz[x];
fa[y1] = y2;
siz[y2] += siz[y1];
ans += w(siz[y2], 2);
}
cout << ans << ' ';
}
return 0;
}
线段树 20pts
暴力20pts;
仔细想想,我们的问题是如何快速求出一个节点的子树的标号和;
不妨设 $ f_{n, x} $ 为一棵有 $ n $ 个叶子的线段树,根的标号是 $ x $ 时的子树和是多少,那么我们不难发现,$ f_{n, x} $ 是关于 $ x $ 的一次函数,且有下列递推式:
我们用一次函数的通用表达形式表达出此递推式,可得出 $ k $ 和 $ b $ 的递推式,记忆化搜索即可;
点击查看代码
#include <iostream>
#include <cstdio>
#include <map>
using namespace std;
const long long mod = 1e9 + 7;
int t;
long long n, x, y;
map<long long, long long> k;
map<long long, long long> b;
inline long long ls(long long x) {
return x << 1;
}
inline long long rs(long long x) {
return x << 1 | 1;
}
long long w(long long a, long long b) {
if (a % b == 0) return a / b;
else return a / b + 1;
}
long long K(long long x) {
if (k[x]) return k[x];
if (x == 0) return 0;
return k[x] = (2 * (K(w(x, 2)) + K(x / 2) % mod) % mod + 1) % mod;
}
long long B(long long x) {
if (b[x] || x == 1) return b[x];
if (x == 0) return 0;
return b[x] = (B(w(x, 2)) + B(x / 2) % mod + k[x / 2] % mod);
}
long long ask(long long id, long long L, long long R, long long l, long long r) {
if (L >= l && R <= r) {
return (k[R - L + 1] * (id % mod) % mod + b[R - L + 1]) % mod;
}
long long mid = (L + R) >> 1;
if (r <= mid) return ask(ls(id), L, mid, l, r);
else if (l > mid) return ask(rs(id), mid + 1, R, l, r);
else return (ask(ls(id), L, mid, l, mid) + ask(rs(id), mid + 1, R, mid + 1, r)) % mod;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> t;
while(t--) {
cin >> n >> x >> y;
k.clear();
b.clear();
k[1] = 1;
b[1] = 0;
K(n);
B(n);
cout << ask(1, 1, n, x, y) << '\n';
}
return 0;
}
魔法师 24pts
暴力模拟24pts;
对于正解,考虑一个法杖 $ p $ 和一个咒语 $ q $,我们要求的答案为 $ \max(a_p + a_q, b_p + b_q) $,也就是说,我们要考虑 $ a_p + a_q - b_p - b_q $ 的正负,进一步地,我们要考虑 $ a_p - b_p $ 和 $ a_q - b_q $ 的大小;
不妨设前一个为 $ x $,后一个为 $ y $,我们需要考虑 $ x, y $ 的大小关系;
若 $ x > y $,即选 $ a_p + a_q $,若 $ x \leq y $,即选 $ b_p + b_q $,我们发现,我们需要一个工具去满足前一个条件,然后同时维护 $ a_p, a_q, b_p, b_q $ 的最小值,更新答案时直接更新即可;
其实由后面的维护最小值的操作,我们想到了线段树,具体地,类似于线段树分治的思想,我们以 $ x $ 和 $ y $ 的值域为下标建一棵线段树,修改时直接单点修改四个值和答案,查询时直接查询根的答案即可;
对于非叶子节点维护四个最值的操作,直接由它的两个儿子合并即可。对于叶子节点,我们开一个 multiset
维护一下四个变量的插入与删除,最后取第一个为最值即可;
对于答案的合并,我们发现,对于一个非叶子节点,其左儿子维护的所有下标是严格小于其右儿子的,这就天然的满足了刚刚说的前一个条件,于是当 $ x > y $ 时(此时相当于法杖在右儿子,咒语在左儿子),我们的答案要从 tr[ls(id)].aq + tr[rs(id)].ap
转移过来,反之同理,从 tr[ls(id)].bp + tr[rs(id)].bq
转移而来;
另记:这题赛时好像把测试点造锅了,教授给把点全换了;
点击查看代码
#include <iostream>
#include <cstdio>
#include <set>
#include <algorithm>
using namespace std;
int n, T;
int s, t, a, b;
int ans;
multiset<int> ap[600005];
multiset<int> bp[600005];
multiset<int> aq[600005];
multiset<int> bq[600005];
namespace seg{
inline int ls(int x) {
return x << 1;
}
inline int rs(int x) {
return x << 1 | 1;
}
struct sss{
int l, r, ap, bp, aq, bq, ans;
}tr[3000005];
inline void push_up(int id) {
tr[id].ans = min({tr[ls(id)].ans, tr[rs(id)].ans, tr[ls(id)].aq + tr[rs(id)].ap, tr[ls(id)].bp + tr[rs(id)].bq});
tr[id].ap = min(tr[ls(id)].ap, tr[rs(id)].ap);
tr[id].aq = min(tr[ls(id)].aq, tr[rs(id)].aq);
tr[id].bp = min(tr[ls(id)].bp, tr[rs(id)].bp);
tr[id].bq = min(tr[ls(id)].bq, tr[rs(id)].bq);
}
void bt(int id, int l, int r) {
tr[id].l = l;
tr[id].r = r;
tr[id].ap = tr[id].bp = tr[id].aq = tr[id].bq = tr[id].ans = 0x3f3f3f3f;
if (l == r) return;
int mid = (l + r) >> 1;
bt(ls(id), l, mid);
bt(rs(id), mid + 1, r);
}
void add(int id, int pos) {
if (tr[id].l == tr[id].r) {
if (t == 0) {
ap[tr[id].l].insert(a);
bp[tr[id].l].insert(b);
tr[id].ap = *ap[tr[id].l].begin();
tr[id].bp = *bp[tr[id].l].begin();
}
if (t == 1) {
aq[tr[id].l].insert(a);
bq[tr[id].l].insert(b);
tr[id].aq = *aq[tr[id].l].begin();
tr[id].bq = *bq[tr[id].l].begin();
}
tr[id].ans = min(tr[id].aq + tr[id].ap, tr[id].bp + tr[id].bq);
return;
}
int mid = (tr[id].l + tr[id].r) >> 1;
if (pos <= mid) add(ls(id), pos);
else add(rs(id), pos);
push_up(id);
}
void del(int id, int pos) {
if (tr[id].l == tr[id].r) {
if (t == 0) {
ap[tr[id].l].erase(a);
bp[tr[id].l].erase(b);
if (ap[tr[id].l].empty()) tr[id].ap = 0x3f3f3f3f;
else tr[id].ap = *ap[tr[id].l].begin();
if (bp[tr[id].l].empty()) tr[id].bp = 0x3f3f3f3f;
else tr[id].bp = *bp[tr[id].l].begin();
}
if (t == 1) {
aq[tr[id].l].erase(a);
bq[tr[id].l].erase(b);
if (aq[tr[id].l].empty()) tr[id].aq = 0x3f3f3f3f;
else tr[id].aq = *aq[tr[id].l].begin();
if (bq[tr[id].l].empty()) tr[id].bq = 0x3f3f3f3f;
else tr[id].bq = *bq[tr[id].l].begin();
}
tr[id].ans = min(tr[id].aq + tr[id].ap, tr[id].bp + tr[id].bq);
return;
}
int mid = (tr[id].l + tr[id].r) >> 1;
if (pos <= mid) del(ls(id), pos);
else del(rs(id), pos);
push_up(id);
}
}
using namespace seg;
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> T;
bt(1, 1, 6e5);
for (int i = 1; i <= n; i++) {
cin >> s >> t >> a >> b;
if (T == 1) {
a ^= ans;
b ^= ans;
}
if (s == 1) {
if (t == 0) {
add(1, a - b + 3e5);
}
if (t == 1) {
add(1, b - a + 3e5);
}
}
if (s == 2) {
if (t == 0) {
del(1, a - b + 3e5);
}
if (t == 1) {
del(1, b - a + 3e5);
}
}
ans = tr[1].ans;
if (ans == 0x3f3f3f3f) ans = 0;
cout << ans << '\n';
}
return 0;
}
园艺 38pts
没看懂部分分在干啥,然后打了个假做法38pts,也没搞懂测试点是咋造的,赛后改的暴力都能A,然后被5k和k8 Hack掉了;
看到这种题,很容易想到DP,其实也可以想到最短路;
然后就可以做了
然后斜率优化一下就做完了。。。
点击查看代码
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;
long long n, k;
long long d[3000005];
long long s[3000005], f[3000005];
long long q1[3000005], q2[3000005];
int l1, l2, r1, r2;
double K1(int x, int y) {
return 1.00 * (1.00 * (f[y] - 2 * y * s[y] + 2 * s[y]) - 1.00 * (f[x] - 2 * x * s[x] + 2 * s[x])) / (1.00 * (-y + x));
}
double K2(int x, int y) {
return 1.00 * (1.00 * (f[y] + 2 * n * s[y] - 2 * y * s[y]) - 1.00 * (f[x] + 2 * n * s[x] - 2 * x * s[x])) / (1.00 * (-y + x));
}
void add1(int x) {
while(l1 < r1 && K1(q1[r1], q1[r1 - 1]) >= K1(q1[r1], x)) r1--;
q1[++r1] = x;
}
void add2(int x) {
while(l2 < r2 && K2(q2[r2], q2[r2 - 1]) <= K2(q2[r2], x)) r2--;
q2[++r2] = x;
}
void ans1(int x) {
if (l1 > r1) return;
while(l1 < r1 && K1(q1[l1 + 1], q1[l1]) <= 2 * s[x]) l1++;
f[x] = f[q1[l1]] + 2 * (q1[l1] - 1) * (s[x] - s[q1[l1]]);
}
void ans2(int x) {
if (l2 > r2) return;
while(l2 < r2 && K2(q2[l2 + 1], q2[l2]) >= 2 * s[x]) l2++;
f[x] = f[q2[l2]] + 2 * (n - q2[l2]) * (s[q2[l2]] - s[x]);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
for (long long i = 1; i <= n - 1; i++) {
cin >> d[i];
s[i + 1] = s[i] + d[i];
}
memset(f, 0x3f, sizeof(f));
f[k] = 0;
for (long long i = 1; i <= n; i++) {
f[k] += abs(s[k] - s[i]);
}
int l = k;
int r = k;
l1 = 1;
r1 = 0;
l2 = 1;
r2 = 0;
while(l >= 1 && r <= n) {
if (f[l] < f[r]) {
add1(l);
ans1(r);
l--;
ans2(l);
} else {
add2(r);
ans2(l);
r++;
ans1(r);
}
}
cout << min(f[1], f[n]);
return 0;
}
To be continued?
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?