牛客寒假算法基础集训营1 B 炸鸡块君与FIFA22(倍增)
题目大意:
给出一个字符串表示每局游戏的结果,每次询问格式为 \((l, r, s)\),询问若你初始有 \(s\) 分,按从左到右的顺序经历了 \([l, r]\) 这一子串的游戏结果后,最终分数是多少。
思路:
对于模 3 意义下相同的初始分数 \(s_1\),经历了 \([l_1, r_1]\) 这一段游戏结果后的变化量是相同的。
我们考虑使用倍增加速查询的过程。
设计状态 \(go[k][i][j]\) 表示在初始分数为 \(k\) 的情况下,经历了\([j, j + 2^i - 1]\)这段游戏后的分数变化量。
每次转移时注意由相应的初始分数状态得到即可。
关于倍增怎样跳着去下一个状态:
对于一个区间 \([L, R]\),区间长度 len 是一个定值,对应一个唯一的二进制表示,每一位的 0/1 就表示我们对于 \(2^i\) 的选取情况,并且选取情况也是唯一的。
例如 8 的二进制表示为 1000,7 的二进制表示为 0111,如果不选择 \(2^3\),那么后面就算全选也达不到 \(2^3\),所以选取情况唯一。
Code:
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int n, q; cin >> n >> q;
string s; cin >> s;
s = " " + s;
auto mod3 = [&](int x) {
return (x % 3 + 3) % 3;
};
//go[k][i][j] 表示在初始分数为k的情况下,经历了[j, j + 2^i - 1]这段游戏后的分数变化量
vector<vector<vector<int>>> go(3, vector<vector<int>>(24 + 1, vector<int>(n + 1)));
for (int j = 1; j <= n; j++) {
if (s[j] == 'W')
for (int k = 0; k < 3; k++)
go[k][0][j] = 1;
else if (s[j] == 'L')
go[0][0][j] = 0, go[1][0][j] = -1, go[2][0][j] = -1;
}
for (int i = 1; i <= 24; i++) {
for (int j = 1; j <= n; j++) {
int m = j + (1 << (i - 1));
if (m <= n) // 没越界
for (int k = 0; k < 3; k++)
go[k][i][j] = go[k][i - 1][j] + go[mod3(k + go[k][i - 1][j])][i - 1][m];
}
}
while (q--) {
int l, r, s; cin >> l >> r >> s;
int now = l;
while (now <= r) {
int power = 0;
while (now + (1 << power) - 1 <= r) //跳多少
power++;
power--;
s += go[s % 3][power][now]; //根据分数模3结果访问对应值
now += (1 << power);
}
cout << s << "\n";
}
return 0;
}