10月28日模拟赛题解
A
Description
机房来了新一届的学弟学妹,邪恶的chenzeyu97发现一位学弟与他同名,于是他当起了善良的学长233
“来来来,学弟,我考你道水题检验一下你的水平……”
一个栈内初始有n个红色和蓝色的小球,请你按照以下规则进行操作
只要栈顶的小球是红色的,将其取出,直到栈顶的球是蓝色
然后将栈顶的蓝球变成红色
最后放入若干个蓝球直到栈中的球数为n
以上3步骤为一次操作
如栈中都是红色球,则操作停止,请问几次操作后停止
chenzeyu97出完题发现他自己不能AC所以想请你帮忙
Limitations
\(1 \leq n \leq 50\)
Solution
hzwer的做法是找规律来着,但是我这种人就不可能找规律了,这辈子不可能找规律的。表也不会打,只能推推式子才能维持得了生活这样子。
首先一个事实是将某个球改变颜色,无论如何变化,发生变化的只是这个球和上方的球,与下方的球无关。
同时注意到可以将一个蓝球变成红球当且仅当这个球上方都是红球。并且变化完以后上方的球都会变成蓝球。
设 \(f_{i}\) 为 \(i\) 是蓝球,且 \(1 \sim (i - 1)\) 都是红球时,将 \(i\) 变成红球且上方也都是红球的操作数。
这种情况下首先需要 \(1\) 次操作将 \(i\) 变成红球,同时上方的球会都会变成蓝色,因此我们需要求出将上方的都都变成红色的操作数。
设 \(g_i\) 为 \(i\) 是蓝球,且 \(1 \sim (i - 1)\) 也都是蓝球时,将这些球都变成红球的操作数。
因此有
\[f_i = 1 + g_{i - 1}\]
考虑求 \(g\),这种情况下需要先将 \(1 \sim (i -1)\) 的球都变成红球,才能将 \(i\) 变成红球,同时上方又会都变成蓝球。然后需要用同样的方式把上方都变成红球。
因此有
\[g_i = 2 \times g_{i - 1} + 1\]
通过上面两个式子就可以算出每个 \(f\),当某位置时蓝球时,将答案加上该位置的 \(f\) 即可。
时间复杂度 \(O(n)\)。
当然,我们考虑记红球为 \(0\),蓝球为 \(1\),将整个栈依次写成一个 \(0/1\) 序列,栈顶元素在右侧,栈底元素在左侧。考虑进行一次操作相当于找最靠右侧的 \(1\),将这个位置变成 \(0\),然后将这个位置右侧的 \(0\) 都改成 \(1\)。而这就是对这个 \(01\) 串的二进制值减一。而减到 \(0\) 停止,因此操作次数就是这个二进制串的值。也可以 \(O(n)\) 求出。
Code
#include
const int maxn = 55;
ll n, ans;
char S[maxn];
ll f[maxn], g[maxn], h[maxn];
int main() {
scanf("%d\n%s", &n, S + 1);
for (int i = 1; i <= n; ++i) {
int di = i - 1;
f[i] = 1 + g[di];
g[i] = (g[di] << 1) + 1;
}
for (int i = 1; i <= n; ++i) if (S[i] == 'B') {
ans += f[i];
}
qw(ans, '\n', true);
return 0;
}
B 魔方
Description
ccy(ndsf)觉得手动复原魔方太慢了,所以他要借助计算机。
ccy(ndsf)家的魔方都是333的三阶魔方,大家应该都见过。
(交换 \(3,4\) 的图片,即以文字为准)
ccy(ndfs)从网上搜了一篇攻略,并找人翻译成了他自己会做的方法。现在告诉你他的魔方情况,以及他从网上搜到的攻略(攻略是一个只含字符 1
2
3
4
的字符串,对应上面四种操作),请你求出最后魔方变成什么样子。
Limitations
攻略长度不超过 \(100\)
Solution
大模拟。其实把每个小方块作为单位存下来应该要比存每个面上的数字稍微好写一点。现场写的是维护每个面上的数字的,写起来有点麻烦。
Code
#include
#include
#include
const int maxn = 105;
const int maxt = 5;
struct Cube {
int mat[maxt][maxt];
Cube() { memset(mat, 0, sizeof mat); }
void init() {
static char tmp[5];
for (int i = 1; i <= 3; ++i) {
scanf("%s", tmp + 1);
for (int j = 1; j <= 3; ++j) {
this->mat[i][j] = tmp[j] - '0';
}
}
}
void swap(bool IsRight, Cube &_others) {
if (IsRight) {
for (int i = 1; i <= 3; ++i) {
std::swap(this->mat[i][3], _others.mat[i][3]);
}
} else {
for (int i = 1; i <= 3; ++i) {
std::swap(this->mat[1][i], _others.mat[1][i]);
}
}
}
void print() {
for (int i = 1; i <= 3; ++i) {
for (int j = 1; j <= 3; ++j) {
qw(this->mat[i][j], ' ', false);
}
putchar('\n');
}
}
void turn(const bool IsClock) {
if (IsClock) {
std::swap(this->mat[1][1], this->mat[3][1]);
std::swap(this->mat[3][1], this->mat[3][3]);
std::swap(this->mat[3][3], this->mat[1][3]);
std::swap(this->mat[1][2], this->mat[2][1]);
std::swap(this->mat[2][1], this->mat[3][2]);
std::swap(this->mat[3][2], this->mat[2][3]);
} else {
std::swap(this->mat[1][1], this->mat[1][3]);
std::swap(this->mat[1][3], this->mat[3][3]);
std::swap(this->mat[3][3], this->mat[3][1]);
std::swap(this->mat[1][2], this->mat[2][3]);
std::swap(this->mat[2][3], this->mat[3][2]);
std::swap(this->mat[3][2], this->mat[2][1]);
}
}
};
Cube cub[10];
char op[maxn];
void Change(const int id, std::vector worklist);
int main() {
scanf("%s", op + 1);
for (int i = 1; i <= 6; ++i) {
cub[i].init();
}
for (char *p = op + 1; *p; ++p) {
switch (*p) {
case '1' : {
Change(1, {1, 6, 2, 5});
break;
}
case '2' : {
Change(2, {1, 5, 2, 6});
break;
}
case '3' : {
Change(4, {1, 3, 2, 4});
break;
}
case '4' : {
Change(3, {1, 4, 2, 3});
break;
}
}
}
for (int i = 1; i <= 6; ++i) {
cub[i].print();
}
return 0;
}
void Change(const int id, std::vector worklist) {
for (int i = 0; i < 3; ++i) {
cub[worklist[i]].swap(id < 3, cub[worklist[i + 1]]);
}
if (id < 3) {
cub[4].turn(id == 1);
} else {
cub[5].turn(id == 4);
}
}
C czy的后宫
Description
czy要妥善安排他的后宫,他想在机房摆一群妹子,一共有n个位置排成一排,每个位置可以摆妹子也可以不摆妹子。有些类型妹子如果摆在相邻的位置(隔着一个空的位置不算相邻),就不好看了。假定每种妹子数量无限,求摆妹子的方案数对大质数取模的结果。
Limitations
\(1 \leq n \leq 10^9\),\(1 \leq m \leq 100\)
Solution
yLOI2019警告
前两天刚做了 GT考试 就碰到这个题了哎
考虑 \(dp\) 一下,设 \(f_{i, j}\) 是考虑前 \(i\) 个位置,第 \(i\) 个位置放妹子 \(j\) 的方案数。转移时可以枚举上一个位置放的哪个妹子。有:
\[f_{i, j} = \sum_{k = 0}^m f_{i - 1, k} \times g_{k, j}\]
其中 \(g_{x, y}\) 维护 \(x\) 和 \(y\) 妹子能否摆在一起,能则返回 \(1\),否则返回 \(0\)。
注意到转移的部分类似矩阵乘法,我们从矩阵的方向来考虑这个转移。
我们固定 \(f\) 的第一维为 \(x\),考虑所有的 \(f_{x, j}\) 组成了一个 \(1 \times (j + 1)\) 的矩阵 \(F_x\),而转移则相当于将这个矩阵乘上一个 \(m \times m\) 的矩阵 \(G\),乘出来的结果是 \(F_{x + 1}\)。
注意这里 \(f_{i, j}\) 不是一个矩阵,固定这个矩阵第一维 \(x\) 后的所有第二维 \(f_{x, j}~~(0 \leq j \leq m)\) 才组成了一个矩阵。
因此得到 \(F_{x} = F_{x - 1} \times G\),因此有 \(F_{n} = F_{0} \times G^n\)。矩阵快速幂即可。
时间复杂度 \(O(m^3 \times \log n)\)。
Code
#include
#include
const int maxn = 105;
const int MOD = 1000000007;
struct Mat {
int x, y;
ll mat[maxn][maxn];
Mat(const int _x = 0, const int _y = 0) : x(_x), y(_y) {
memset(mat, 0, sizeof mat);
}
Mat operator*(const Mat &_others) const {
Mat _ret(this->x, _others.y);
for (int i = 0; i <= x; ++i) {
for (int j = 0; j <= _others.y; ++j) {
for (int k = 0; k <= y; ++k) {
(_ret.mat[i][j] += this->mat[i][k] * _others.mat[k][j]) %= MOD;
}
}
}
return _ret;
}
void operator~() {
for (int i = 0; i <= x; ++i) {
this->mat[i][i] = 1;
}
}
};
Mat F, G;
int n, m, ans;
Mat mpow(int y);
int main() {
freopen("harem.in", "r", stdin);
freopen("harem.out", "w", stdout);
qr(n); qr(m);
for (int i = 1; i <= m; ++i) {
ll &ch = G.mat[i][1];
ch = '1' - IPT::GetChar();
while ((ch != 0) && (ch != 1)) ch = '1' - IPT::GetChar();
for (int j = 2; j <= m; ++j) {
G.mat[i][j] = '1' - IPT::GetChar();
}
G.mat[i][0] = 1;
}
for (int i = 0; i <= m; ++i) {
G.mat[0][i] = 1;
}
G.x = G.y = m;
F.x = 0; F.y = m;
F.mat[0][0] = 1;
F = F * mpow(n);
for (int i = 0; i <= m; ++i) {
(ans += F.mat[0][i]) %= MOD;
}
qw(ans, '\n', true);
return 0;
}
Mat mpow(int y) {
Mat _ret(m, m); ~_ret;
while (y) {
if (y & 1) {
_ret = _ret * G;
}
G = G * G;
y >>= 1;
}
return _ret;
}
D mex
Description
给定一个长度为 \(n\) 的序列 \(A\),有 \(q\) 次查询,每次查询区间 \(mex\)。
Limitations
所有数据均是不大于 \(2 \times 10^5\) 的非负整数。
Solution
不强制在线,考虑莫队。
对序列的值域也进行分块,每块维护块内有多少数字已经出现了。这样在查找 \(mex\) 时只需要顺序扫一遍所有的块,找到最小的出现数字数不等于块大小的块,在块内暴力枚举即可。而移动莫队指针时,对块内进行的修改是 \(O(1)\) 的。
一共有 \(O(n \sqrt{n})\) 次指针移动,每次 \(O(1)\),因此移动指针的复杂度是 \(O(n \sqrt n)\)
一共有 \(O(q)\) 次查询,每次查询的复杂度是 \(O(\sqrt A)\) 的,其中 \(A\) 代表序列 \(A\) 的最大元素,因此查询复杂度 \(O(q \sqrt A)\)。
总时间复杂度 \(O(n \sqrt n + q \sqrt A)\)。
当然,如果强制在线也能做,考虑对序列维护一个可持久化权值线段树,每棵线段树维护这个位置的前缀的每个数字的最晚位置。进行一次 \(query~(l,~r)\) 的查询时,只需要查询第 \(r\) 棵树上最小的出现最晚位置小于 \(l\) 的节点。这个东西可以通过在线段树上二分做到时间复杂度 \(O(\log A)\)。一共有 \(O(q)\) 次查询,建树 \(O(n \log A)\) 因此总时间复杂度 \(O((n + q)\log A)\)。
Code
#include
#include
#include
const int maxn = 200005;
int n, q, maxv, base;
int MU[maxn], belong[maxn], size[maxn], val[maxn], vis[maxn], br[maxn], bl[maxn];
struct Ask {
int l, r, id, ans;
inline bool operator<(const Ask &_others) const {
if (belong[this->l] != belong[_others.l]) {
return this->l < _others.l;
} else if (belong[this->l] & 1) {
return this->r < _others.r;
} else {
return this->r > _others.r;
}
}
};
Ask ask[maxn];
void add(const int v);
void del(const int v);
bool cmp(const Ask &a, const Ask &b);
int main() {
freopen("mex.in", "r", stdin);
freopen("mex.out", "w", stdout);
qr(n); qr(q);
for (int i = 1; i <= n; ++i) {
qr(MU[i]);
if (MU[i] > maxv) maxv = MU[i];
}
++maxv;
base = sqrt(maxv);
for (int i = 0; i <= maxv; ++i) {
++size[belong[i] = i / base];
br[belong[i]] = i;
}
for (int i = 1; br[i]; ++i) {
bl[i] = br[i - 1] + 1;
}
for (int i = 1; i <= q; ++i) {
Ask &u = ask[i];
qr(u.l); qr(u.r); u.id = i;
}
std::sort(ask + 1, ask + 1 + q);
for (int i = 1, prel = ask[1].l, prer = prel - 1; i <= q; ++i) {
int l = ask[i].l, r = ask[i].r;
while (prel > l) add(MU[--prel]);
while (prer < r) add(MU[++prer]);
while (prel < l) del(MU[prel++]);
while (prer > r) del(MU[prer--]);
int p = 0;
while (val[p] == size[p]) ++p;
p = bl[p];
while (vis[p]) ++p;
ask[i].ans = p;
}
std::sort(ask + 1, ask + 1 + q, cmp);
for (int i = 1; i <= q; ++i) {
qw(ask[i].ans, '\n', true);
}
return 0;
}
void add(const int v) {
if (!(vis[v]++)) {
++val[belong[v]];
}
}
void del(const int v) {
if (!(--vis[v])) {
--val[belong[v]];
}
}
inline bool cmp(const Ask &a, const Ask &b) {
return a.id < b.id;
}