牛客寒假算法基础集训营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;
}
posted @ 2022-01-31 15:02  Nepenthe8  阅读(37)  评论(0编辑  收藏  举报