[赛记] 多校A层冲刺NOIP2024模拟赛03

五彩斑斓(colorful)100pts

赛时2h+就搞这道题了,一直以为是签到,结果是T3?然后T3就没时间打了。。。

在这道题中,在一行/一列的两个点也算子矩形;

考虑用所有子矩形数减去四个点都相同的子矩形数,问题转变成如何求后者;

发现上面两个点和下面两个点是平行的,所以我们可以枚举左右两个点的距离,然后枚举左边点在哪一列哪一行,这样我们就定住了四个点的位置,然后存一下两个点相同的位置和颜色,用首项加尾项那个公式求一下即可;

注意要统计在一行/一列的两个点;

时间复杂度:$ \Theta(nm^2) $;

点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
int n, m;
int a[505][505];
int mp[1000005], tot;
long long ans, su;
long long sum[1000005];
int rem[1000005];
int main() {
	freopen("colorful.in", "r", stdin);
	freopen("colorful.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> a[i][j];
			if (!mp[a[i][j]]) mp[a[i][j]] = ++tot;
			a[i][j] = mp[a[i][j]]; //其实不用离散化;
		}
	}
	for (int d = 0; d <= m - 1; d++) {
		for (int j = 1; j <= m - d; j++) {
			for (int i = 1; i <= n; i++) {
				if (a[i][j] == a[i][j + d]) {
					ans -= ((sum[a[i][j]] * (sum[a[i][j]] + 1)) / 2);
					sum[a[i][j]]++;
					rem[++rem[0]] = a[i][j];
					ans += ((sum[a[i][j]] * (sum[a[i][j]] + 1)) / 2);
				}
			}
			for (int i = 1; i <= rem[0]; i++) {
				sum[rem[i]] = 0;
			}
			rem[0] = 0;
		}
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			su += (m - j + 1);
			su += (m - j + 1) * (n - i);
		}
	}
	cout << su - ans;
	return 0;
}

错峰旅行(travel)50pts

还是暴力50pts;

对于正解,考虑怎样优化我们乘法原理的遍历计数过程,发现有很多段可选的城市数是相同的,对于这些段我们用快速幂即可解决;

考虑如何维护这些段,发现对于题目中给的条件,在开头处会将城市总数-1,结尾+1,所以我们可以维护一个差分的形式(这几次经常出,而且经常想不到,注意一下),将初始城市数设为 $ n $,每次按照扫描线的写法,碰到一个开头就将城市总数-1,结尾+1,中间的部分用快速幂算一下即可;

时间复杂度:$ \Theta(m \log m) $,瓶颈在排序;

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const long long mod = 1e9 + 7;
int n, m, s, t;
struct sss{
	int x, f;
	bool operator <(const sss &A) const {
		return x < A.x;
	}
}e[5000005];
int cnt;
long long ksm(long long a, long long b) {
	long long ans = 1;
	while(b) {
		if (b & 1) ans = ans * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return ans;
}
long long ans;
int main() {
	freopen("travel.in", "r", stdin);
	freopen("travel.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m >> s >> t;
	int x, l, r;
	for (int i = 1; i <= m; i++) {
		cin >> x >> l >> r;
		e[++cnt] = {l, -1};
		e[++cnt] = {r + 1, 1};
	}
	sort(e + 1, e + 1 + cnt);
	int i = 1;
	while(i <= cnt) {
		int now = i;
		while(e[i].x == e[now + 1].x) {
			e[i].f += e[now + 1].f;
			e[now + 1].x = -1;
			now++;
		}
		i = now + 1;
	}
	sort(e + 1, e + 1 + cnt);
	int pos = 0;
	for (int i = 1; i <= cnt; i++) {
		if (e[i].x != -1) {
			pos = i;
			break;
		}
	}
	int now = n;
	l = 0;
	r = s - 1;
	ans = 1;
	for (int i = pos; i <= cnt; i++) {
		l = r + 1;
		r = e[i].x - 1;
		ans = ans * ksm(now, r - l + 1) % mod;
		now += e[i].f;
	}
	l = r + 1;
	r = t;
	ans = ans * ksm(now, r - l + 1) % mod;
	cout << ans;
	return 0;
}

线段树(segment)0pts

赛时没时间打,结果交了个这个。。。

点击查看HH
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
int n, q;
struct sss{
	int l, r;
}e[500005];
int main() {
	freopen("segment.in", "r", stdin);
	freopen("segment.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> q;
	for (int i = 1; i <= q; i++) {
		cin >> e[i].l >> e[i].r;
	}
	if (q <= 2) {
		cout << q;
	} else {
		cout << ceil(q + log2(q));
	}
	return 0;
}

然后0pts;

对于正解,区间DP(比较难想到,主要是不太常打,对于 $ n \leq 500 $ 这样的范围还是得注意一下);

我们发现,每个询问需要的块数的下界是1块;

想一想什么时候才会使这个下界上升,我们不难联想到写线段树时的询问操作,如果此询问区间被左子区间完全包含就递归到左子区间,被右子区间完全包含就递归到右子区间,否则进入两个子区间求解

发现在这个线段树区间内,前两种操作并不会对下界造成影响,而第三种操作会导致左边至少有一个,右边至少有一个,所以下界+1;

好了,所以我们只需关注在一个线段树区间进行三操作的次数就行了;

所以定义 $ f_{l, r} $ 表示在线段树区间 $ [l, r] $ 中进行三操作的最小次数;

考虑转移,枚举断点,则 $ f_{l, r} = \min f_{l, k} + f_{k + 1, r} + cost(l, r, k) $;

考虑如何计算 $ cost(l, r, k) $,发现其就代表在所有询问区间中有如下特征的区间个数:

  1. 与这个区间有交集;

  2. 不包含这个区间

  3. 经过断点 $ k $;

  4. $ k $ 不是这个询问区间的左端点;

对于2,就直接返回这个区间的值了,所以根本不用向下递归了;

那么我们预处理出 $ w_k $ 数组表示满足3,4的区间个数,预处理出 $ num_{l, r} $ 数组表示包含线段树区间 $ [l, r] $ 的询问区间个数,那么 $ cost(l, r, k) = w_k - num_{l, r} $;

$ num $ 数组直接二维前缀和处理即可(这个最近也常考,注意一下);

最后输出 $ f_{1, n} + q $ 即可( $ + q $ 是因为块的个数 $ = $ 三操作的个数 $ + 1 $ );

时间复杂度:$ \Theta(\max (qn, n^3)) $;

点击查看代码
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;
int n, q;
int num[505][505];
int f[505][505], w[505];
int main() {
	freopen("segment.in", "r", stdin);
	freopen("segment.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> q;
	int l, r;
	for (int i = 1; i <= q; i++) {
		cin >> l >> r;
		num[l][r]++;
		for (int j = l; j < r; j++) w[j]++;
	}
	for (int len = n; len >= 1; len--) {
		for (int l = 1; l + len - 1 <= n; l++) {
			int r = l + len - 1;
			num[l][r] += num[l - 1][r] + num[l][r + 1] - num[l - 1][r + 1];
		}
	}
	memset(f, 0x3f, sizeof(f));
	for (int i = 1; i <= n; i++) f[i][i] = 0;
	for (int len = 2; len <= n; len++) {
		for (int l = 1; l + len - 1 <= n; l++) {
			int r = l + len - 1;
			for (int k = l; k < r; k++) {
				f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + w[k] - num[l][r]);
			}
		}
	}
	cout << f[1][n] + q;
	return 0;
}
posted @ 2024-10-10 14:43  Peppa_Even_Pig  阅读(13)  评论(0编辑  收藏  举报