[赛记] 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 $ 的一次函数,且有下列递推式:

\[ f_{n, x} = f_{\lceil \frac n2 \rceil, x << 1} + f_{\lfloor \frac n2 \rfloor, x << 1 | 1} \]

我们用一次函数的通用表达形式表达出此递推式,可得出 $ 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?

posted @   Peppa_Even_Pig  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?
点击右上角即可分享
微信分享提示