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 <cstdio>
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 <cstdio>
#include <cstring>
#include <vector>
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<int> 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<int> 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 <cstdio>
#include <cstring>
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 <cmath>
#include <cstdio>
#include <algorithm>
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;
}