[赛记] 多校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) $,发现其就代表在所有询问区间中有如下特征的区间个数:
-
与这个区间有交集;
-
不包含这个区间;
-
经过断点 $ k $;
-
$ 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;
}