ZR 七连 Day 1 游记
ZR 七连 Day 1 游记
游记篇
赛前搞笑事件
今天是第一场正睿,还是要 好好对待 的
$ 17:59:58 $ 还在吃饭
$ 17:59:59 $ 做出重要决定,先打着比赛,有空就吃一口包子
$ 18:00 $ 比赛开始
乐死
比赛开始了
先读一下第一题,发现比较简单,可以使用二维前缀和过掉,于是我写了一个代码,然后寄了
一顿调试,找了几个小错误,然后发现了一个大错,因为统计的是连续的两个方块的数量,所以在求子矩阵的时候需要切掉两排
切掉以后就过了,测了测大样例也过了
$ 18:30:14 $ 提交 $ T1 $
读了读 $ T2 $,然后就一顿吐槽出题人
想了半天,也不会 $ T2 $,然后就去看 $ T3 $
感觉是水题,一眼秒,然后看到数据范围 $ n \le 10^{18} $,当场就炸了,不过考虑到打朴素的暴力有 $ 40 $ 分,还是打了暴力,其实就是一个简单的 $ dp $,然后测了测大样例,过了
$ 19:35:17 $ 提交 $ T3 $
然后看了看 $ T4 $,可以打爆搜,然后就写了一个爆搜交了上去
$ 19:59:47 $ 提交 $ T4 $
预估分数:$ 100 + 0 + 40 + 20 = 160 $
事实上一分不差
之后就玩 $ florr.io $ 去了,还 $ 7 = 1 $ 出了粉 $ Wing $
题解篇
T1 雪豹
这道题的题目意思是寻找一个矩阵中所有的指定宽高的子矩阵中相邻的方块是不同颜色的数量大于 $ k $,颜色只有 $ 0, 1 $ 两种
然后我们一眼就发现是二维前缀和
我们设 $ sum_{i, j} $ 表示从 $ 1, 1 $ 到 $ i, j $ 中相邻方块不同颜色的数量
每次统计的时候只需要记录新的方块的左边和上边是不是不同的,如果是不同的就增加答案,即:
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + (g[i][j - 1] != g[i][j]) + (g[i - 1][j] != g[i][j]);
然后枚举所有的起点,用前缀和判断是否合法,数量这么求:
int nw = sum[k][l] - sum[i - 1][l] - sum[k][j - 1] + sum[i - 1][j - 1];
但是会发现,我们多统计了一点东西,也就是说,我们截出子矩阵,没有断开和原来相连的部分
我们可以这么统计(只以横排为例):
用 $ hh_{i, j} $ 表示第 $ i $ 行与上一行对应方块颜色不同的数量,第二维是前缀和
然后我们在记录答案的时候,要减去这个东西:
nw -= hh[i][l] - hh[i][j - 1];
列也是一样的
然后提交一下就 $ AC $ 了
完整代码如下:
# include <bits/stdc++.h>
# define int long long
using namespace std;
int n, m, h, w, kk;
char g[5005][5005];
int sum[5005][5005], hh[5005][5005], ll[5005][5005];
signed main () {
cin >> n >> m >> h >> w >> kk;
for (int i = 1; i <= n; ++ i) for (int j = 1; j <= m; ++ j) {
cin >> g[i][j];
g[i][j] = (g[i][j] == '1');
}
for (int i = 1; i <= n; ++ i) for (int j = 1; j <= m; ++ j) {
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + (g[i][j - 1] != g[i][j] && j != 1) + (g[i - 1][j] != g[i][j] && i != 1);
hh[i][j] = hh[i][j - 1] + (g[i][j] != g[i - 1][j]);
ll[i][j] = ll[i - 1][j] + (g[i][j] != g[i][j - 1]);
}
int ans = 0;
for (int i = 1; i <= n - h + 1; ++ i) for (int j = 1; j <= m - w + 1; ++ j) {
int k = i + h - 1, l = j + w - 1;
int nw = sum[k][l] - sum[i - 1][l] - sum[k][j - 1] + sum[i - 1][j - 1];
if (i != 1) nw -= hh[i][l] - hh[i][j - 1];
if (j != 1) nw -= ll[k][j] - ll[i - 1][j];
if (nw >= kk) ++ ans;
}
cout << ans << endl;
return 0;
}
T2 猞猁
每次可以在相邻的两个数中把两个数各减 $ 1 $,并且 $ 1 $ 和 $ n $ 相邻,求能否让所有的数都变成 $ 0 $
我们假设在第一个地方操作 $ x $ 次,第二个地方操作 $ a_2 - x $ 次,第三个地方操作 $ a_3 - a_2 + x $ 次,以此类推,第 $ n $ 个地方操作 $ a_n - a_{n - 1} + \dots + (-1)^{n + 1}x $ 次
然后我们发现,在 $ n $ 个地方操作的次数加上第一个地方操作的次数相加等于 $ a_1 $
然后我们就可以发现,如果 $ n $ 是偶数,我们就可以把式子写出来,$ x $ 被抵消了,判断左边和右边是否相等,输出 $ 1 $ 或者 $ 0 $ 即可
如果 $ n $ 是奇数,就把 $ x $ 解出来,然后判断是否在上下界里面,输出 $ 1 $ 或者 $ 0 $ 即可
接下来讲解细节
首先,我们用一个变量 $ b $ 存储 $ a_n - a_{n - 1} + \dots + (-1)^{n}a_2 $
循环,每次 $ b = a_i - b $,然后记录变量 $ k $ 判断正负,每次 $ k = -k $
然后推式子,偶数的情况如下:
$ a_n - a_{n - 1} + \dots + a_2 = b $
所以我们的式子变成了 $ b - x + x = a_1 $
即 $ b = a_1 $
然后我们只需要判断上面的式子是否满足就可以了
奇数的情况如下:
$ a_n - a_{n - 1} + \dots - a_2 + x + x = a_1 $
同样的,我们用 $ b $ 代替前面一坨,得到 $ b + 2x = a_1 $
可以求出 $ x $
接下来求上下界
首先对于每个 $ a_i $,我们需要考虑正负问题,如果当前 $ x $ 是负的,那么 $ x $ 能取到最大值就让式子为 $ 0 $,因为 $ x $ 越大,式子越小,反之,$ x $ 取到的最小值就让式子最大,式子的最大值不能超过 $ a_i $,所以取 $ b - a_i $
如果 $ x $ 是正的,那我们就考虑相反的问题,如果 $ x $ 越大,式子越大,最大不超过 $ a_i $,所以可以取 $ a_i - b $,最小的话,可以取 $ -b $
然后就可以切掉这道题了
完整代码如下:
# include <bits/stdc++.h>
# define int long long
using namespace std;
int n, a[500005];
inline void solve () {
cin >> n;
for (int i = 1; i <= n; ++ i) cin >> a[i];
int k = 1, b = 0, minx = 0, maxx = a[1];
for (int i = 2; i <= n; ++ i) {
k *= -1;
b = a[i] - b;
if (k == 1) {
minx = max (minx, -b);
maxx = min (maxx, a[i] - b);
}
else {
maxx = min (maxx, b);
minx = max (minx, b - a[i]);
}
}
if (k == -1) {
if (b == a[1]) puts ("1");
else puts ("0");
}
else {
int x = (a[1] - b) / 2;
if (x * 2 == a[1] - b) {
if (minx <= x && x <= maxx) puts ("1");
else puts ("0");
}
else puts ("0");
}
}
signed main () {
int t; cin >> t; while (t --) solve ();
return 0;
}
T3 土拨鼠
题意是有一个集合 $ S $,每次可以走集合里的步数,每从一个位置离开,就可以拿走任意一个萝卜,也可以不拿,每个萝卜都不同
求从 $ 1 $ 走到 $ n $ 有多少种不同的方案
很简单,我们可以考虑 dp,设 $ dp_i $ 表示走到位置 $ i $ 的方案,这里不统计在位置 $ i $ 的萝卜拿法
然后我们就考虑位置 $ i $ 有 $ i + 1 $ 种方案
这样我们可以写出转移:
$ dp_i = \sum_{j \in S} dp_{i - j} \times (i - j + 1) $
然后我们发现 $ n $ 很大,所以 dp 行不通,考虑矩阵快速幂
我们初始矩阵竖着排,最上面是 $ dp_i $,最下面是 $ dp_{i - 14} $
然后我们考绿把这个矩阵变成最上面是 $ dp_{i + 1} $
也就是说,第一行需要把能通过当前的 $ dp_j $ 转移到 $ dp_i $ 的数字的对应值设为对 $ 2027 $ 取模的值,之后后面所有的行都直接把行号减一的列对应值设为 $ 1 $,其他的全是 $ 0 $ 就可以了
这样我们得到了 $ 2027 $ 个矩阵,每次的话是轮流乘
这样我们就先把 $ 2027 $ 个矩阵的乘积求出来,然后看看有几个完整的,只有一个不完整的,这样复杂度就大大降低
代码如下:
# include <bits/stdc++.h>
# define int long long
using namespace std;
const int mod = 2027;
struct Mat {
int a[20][20];
Mat () {
memset (a, 0, sizeof (a));
}
inline Mat operator * (const Mat b) const {
Mat ans;
for (int i = 1; i <= 15; ++ i) for (int j = 1; j <= 15; ++ j) for (int k = 1; k <= 15; ++ k) {
(ans.a[i][j] += a[i][k] * b.a[k][j] % mod) %= mod;
}
return ans;
}
inline void init () {
memset (a, 0, sizeof (a));
for (int i = 1; i <= 15; ++ i) a[i][i] = 1;
}
} tzf[3005] ;
int n, m; bool vis[35];
inline Mat ksm (Mat a, int b) {
Mat ans;
ans.init ();
for (; b; b >>= 1, a = a * a) {
if (b & 1) ans = ans * a;
}
return ans;
}
signed main () {
cin >> n >> m;
for (int i = 1; i <= m; ++ i) { int x; cin >> x; vis[x] = 1; }
for (int k = 0; k <= mod - 1; ++ k) {
for (int i = 1; i <= 15; ++ i) if (vis[i]) tzf[k].a[1][i] = k;
for (int i = 2; i <= 15; ++ i) tzf[k].a[i][i - 1] = 1;
}
int s = n / mod, laz = n % mod;
Mat sum;
sum.init ();
for (int i = 1; i <= mod; ++ i) sum = sum * tzf[i % mod];
Mat ans; ans.a[1][1] = 1;
ans = ans * ksm (sum, s);
sum.init ();
for (int i = 1; i <= laz; ++ i) sum = sum * tzf[i % mod];
ans = ans * sum;
cout << ans.a[1][1] << endl;
return 0;
}
T4 芝士
我们考虑二分答案 $ k $,接下来考虑 check 怎么写
我们这么考虑,前两个随便放,然后其他的有一个范围,如果超出了范围就直接返回 $ 0 $
设大范围是 $ L, R $,小范围是 $ l, r $,初始大范围是左边,小范围是右边,如果两个范围都可以的话,就找一个最优的
我们可以把大范围设为两个区间合并后更大的范围,好像就是并,然后小范围就是当前的范围,也就是 $ [a_i - k, a_i + k] $
接下来如果只能满足一个范围,就直接放就可以了,并更新
都不满足就返回 $ 0 $
这是最简单的题,但是思维较难
代码如下:
# include <bits/stdc++.h>
using namespace std;
int n, a[1000005];
inline bool check (int k) {
int l1 = a[1] - k, r1 = a[1] + k;
int l2 = a[2] - k, r2 = a[2] + k;
if (a[2] < l1 || a[2] > r1) return 0;
for (int i = 3; i <= n; ++ i) {
if (a[i] > r1 || a[i] < l1) {
if (a[i] > r2 || a[i] < l2) return 0;
l2 = a[i] - k, r2 = a[i] + k;
}
else if (a[i] > r2 || a[i] < l2) {
l1 = a[i] - k, r1 = a[i] + k;
}
else {
l1 = min (l1, l2);
r1 = max (r1, r2);
l2 = a[i] - k, r2 = a[i] + k;
}
}
return 1;
}
signed main () {
cin >> n; for (int i = 1; i <= n; ++ i) cin >> a[i];
int l = 0, r = 1e9, ans = 0;
while (l <= r) {
int mid = (l + r) >> 1;
if (check (mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
cout << ans << endl;
return 0;
}