【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(2)

比赛链接
本文发布于博客园,会跟随补题进度实时更新,若您在其他平台阅读到此文,请前往博客园获取更好的阅读体验。
跳转链接:https://www.cnblogs.com/TianTianChaoFangDe/p/18773190

开题 + 补题情况

除了 AK 题都补完了,还是学到了很多东西,以及很多平时比赛注意不到的点。
image

总结

这场太唐了,四个签到题做得依托答辩,罚时控制得一点也不好,之后还是应该三思后再交题,没想清楚前不要写代码,不然堆shi!
然后是补的题吧,这场题目质量挺高的(除了某个测评机问题,屮!)。
1006 属于是见识少了,很经典的博弈手法。
1001 可以迁移线性基的思想,按位考虑,位运算的题都尽可能地考虑按位考虑。
1007 DP 好题了属于是,DP 很重要的就是如何找出一个无后效性的状态,这个题就是一个很好的典范,通过扩展状态来避免乘法的后结合而导致难以维护,这题还用了自动机的想法来减少计算次数。
1008 又是对问题的转化没转化好,这个只要转化出每个点只和三个刷子的顺序就很容易想到拓扑了。
1010 很好的数据结构题,顺便复习了一下离散数学函数的相关知识,同时也体现出线段树的一个重要特点,那就是满足结合律,以及使用了前缀和来优化复杂度,通过自己定义逆运算来达成使用前缀和进行区间查询。

1002 - 学历史导致的

签到题,由于数据范围只有几十,所以直接枚举暴搜就行。

点击查看代码
#include <bits/stdc++.h>
#define inf 2e18
#define int long long

const int N = 2e5 + 9;

std::string a[] = {"jia", "yi", "bing", "ding", "wu", "ji", "geng", "xin", "ren", "gui"};
std::string b[] = {"zi", "chou", "yin", "mao", "chen", "si", "wu", "wei", "shen", "you", "xu", "hai"};

void solve()
{
    std::string s;std::cin >> s;

    for(int i = 1984;i <= 2043;i ++) {
        int ch = i - 1984;
        int x = ch % 10;
        int y = ch % 12;
        std::string t = a[x] + b[y];
        if(s == t) {
            std::cout << i << '\n';
            return;
        }
    } 
}

1004 - 学 DP 导致的

注意到最长答案只会是 \(26\),所以最多复制26次就行。
乍一看以为要用单调栈优化法或线段树优化法找最长上升子序列,结果定睛一看,都是小写字母,那范围也就 \(26\),直接暴力 DP 就行了。

点击查看代码
#include <bits/stdc++.h>
#define inf 2e18
#define int long long

const int N = 2e5 + 9;

int toint(std::string &s) {
    int res = 0;
    for(auto &i : s) {
        res = res * 10 + i - '0';
    }
    return res;
}

void solve()
{
    std::string s;std::cin >> s;
    std::string k;std::cin >> k;

    int x;
    if(k.size() > 2) {
        x = 100;
    } else {
        x = toint(k);
    }

    std::string tmp = s;
    for(int i = 1;i < x;i ++) {
        s += tmp;
    }

    std::vector<int> dp(27, 0);

    auto getint = [](const char c) -> int {
        return c - 'a' + 1;
    };

    int ans = 0;
    for(auto &i : s) {
        int ix = getint(i);
        for(int j = 0;j < ix;j ++) {
            dp[ix] = std::max(dp[ix], dp[j] + 1);
        }
        ans = std::max(ans, dp[ix]);
    }

    std::cout << ans << '\n';
}

1003 - 学数数导致的

一开始读错题了,写了半天,以为是找不同的下标构成的题目的构造,然后定睛一看是找不同的数字构成的,这难度一下就下降了不少。
我们可以动态维护一下当前前缀每个数字的总数,然后从后往前遍历,记录一下后面出现了多少个不同的正整数 \(sum\),然后对于每一个数字,如果有间隔 \(0\) 的它自己,就更新一下它的答案最大值,也就是当前的 \(sum\),最后求一个和。
这个题有一个很关键的点在于要以 \(0\) 为分界点清除每个数字作为 \(p\) 的数量,否则可能不间隔 \(0\) 也被算进去了。

点击查看代码
#include <bits/stdc++.h>
#define inf 2e18
#define int long long

const int N = 1e6;

void solve()
{
    int n;std::cin >> n;
    std::vector<int> a(n);

    for(auto &i : a) {
        std::cin >> i;
    }

    std::vector<int> cnt(N + 9, 0), all(N + 9, 0);
    std::vector<bool> vis(N + 9, 0);

    for(int i = 0;i < n;i ++) {
        cnt[a[i]] ++;
    }

    int ans = 0;
    int sum = 0;
    vis[0] = true;
    for(int i = n - 1;i >= 0;i --) {
        if(a[i] == 0) {
            cnt[a[i]] --;
            continue;
        }

        if(!cnt[0])break;

        int ix;
        for(int j = i;j >= 0 && a[j] != 0;j --) {
            cnt[a[j]] --;
            ix = j;
        }

        for(int j = i;j >= 0 && a[j] != 0;j --) {
            if(a[j] && cnt[a[j]]) {
                all[a[j]] = std::max(all[a[j]], sum);
            }
            sum += !vis[a[j]];
            vis[a[j]] = true;
        }
        i = ix;
    }

    for(int i = 1;i <= N;i ++) {
        ans += all[i];
    }

    std::cout << ans << "\n";
}

1005 - 学几何导致的

不难发现,垂直的,其实就是旋转了 \(90\) 度的,也就是说:\((180 / k) \times x = 90 \times p\)\(p\) 为奇数,移项消元后可以得到:\(2 \times x = k \times p\),式子左边一定是一个偶数,\(p\) 是一个奇数,若要让式子成立,\(k\) 必须是一个偶数,因此所有奇数 \(k\) 答案都是 \(0\)
然后,我们求出的 \(x\),也就是一个 \(90\) 度的循环,那么我们按 \(x\) 进行分块,对于奇数块中的线,和它垂直的就是偶数块中的对应位置的线,对于偶数块中的线,和它垂直的就是奇数块中的对应位置的线。
我们首先将最后对 \(2 \times x\) 取模后不完整的块往前匹配特殊计算一下。
然后对于剩下的,为了避免计算重复,每个块只和后面的块匹配,不难发现对于奇数块可以得到一个 \(1\) ~ \(n / 2x\) 的公差为 \(1\) 的等差数列,对于偶数块可以得到一个 \(1\) ~ \(n / 2x - 1\) 的公差为 \(1\) 的等差数列,分别对等差数列求和后,再乘一个 \(x\) 将块中的元素个数的贡献加上来即可。

点击查看代码
#include <bits/stdc++.h>
#define inf 2e18
#define int long long

const int N = 2e5 + 9;

void solve()
{
    int n, k;std::cin >> n >> k;
    int ans = 0;

    if(k & 1) {
        std::cout << 0 << '\n';
        return;
    }

    int x = k / 2;

    int ch = n % x;
    n -= ch;

    int p = n / x;
    if(p & 1) {
        int sum = n / (2 * x) + 1;
        ans += ch * sum;
    } else {
        int sum = n / (2 * x);
        ans += ch * sum;
    }

    ch = n % (2 * x);
    n -= ch;
    if(ch) {
        int sum = n / (2 * x);
        ans += ch * sum;
    }

    int sum = n / (2 * x);
    if((sum + 1) & 1) {
        ans += sum / 2 * (sum + 1) * x;
        ans += sum / 2 * (sum - 1) * x;
    } else {
        ans += (sum + 1) / 2 * sum * x;
        ans += (sum - 1) / 2 * sum * x;
    }

    std::cout << ans << '\n';
}

1006 - 学博弈论导致的(补题)

被博弈论创似了。
首先宝盒是没有用的,因为操作完宝盒后,无论如何生成宝石,下一个人都可以通过相应的拿走宝石的操作拿走生成的宝石,从而使场上剩余的宝石数量和拿宝盒之前一致,这样推下去,宝箱拿完后,宝石的数量仍然没有变化,并且先后手关系也还没有变化。
然后就只用关注宝石了,如果只有红宝石的话,很明显是一个 NIM 游戏,也就是 \(\mod 4 = 0\) 的时候 Bob 胜利,否则 Alice 胜利。
而对于蓝宝石,由于他可能会携带红宝石,或者生成红宝石,所以我们对它进行量化,我们把红宝石设置为 \(1\),由于我们想把蓝宝石的操作也量化到 \(1\) ~ \(3\) 内,所以把蓝宝石设置为 \(2\),因为有一个操作是红蓝一起拿,此时这个操作代价为 \(3\),这样的话,对于蓝宝石的操作,也可以用数字进行量化了:

  • 蓝宝石变红宝石,也就是 \(2\)\(1\),相当于拿走 \(1\)
  • 拿走一块蓝宝石,拿走一块红宝石,相当于拿走 \(3\)
  • 拿走一块蓝宝石,不拿走红宝石,相当于拿走 \(2\)
  • 拿走两块蓝宝石,生成一块红宝石,相当于拿走 \(4 - 1 = 3\),但其实这个操作没用,因为一块蓝宝石足以达到 \(1\) ~ \(3\) 随意选择。

这时所有操作都被量化到 \(1\) ~ \(3\) 了,并且拿走一块蓝宝石或者一块红宝石就可以达到 \(1\) ~ \(3\) 内随意选择,因此就转化为了一个 NIM 游戏。
所以最终结论就是,红宝石数量加上蓝宝石数量的两倍的和是 \(4\) 的倍数就是 Bob 获胜,否则 Alice 获胜。

点击查看代码
#include <bits/stdc++.h>
#define inf 2e18
#define int long long

const int N = 2e5 + 9;

void solve()
{
    int x, y, z;std::cin >> x >> y >> z;

    if((x + 2 * y) % 4 == 0) {
        std::cout << "Bob\n";
    } else {
        std::cout << "Alice\n";
    }
}

1008 - 学画画导致的(补题)

我们注意到,对于每一块,只可能有三把刷子刷到它,而如果这一块的答案已经给出,则这一块一定是这三把刷子中最后刷的,另外两把刷子得先刷,那么其实也就形成了一个拓扑序列。
我们以每把刷子为结点进行建图。
我们对于每一个已知颜色的块,首先判断一下这个颜色能否刷到这一块,如果不能,则一定无法形成所给颜色构造,然后将另外两把刷子向这把刷子连边,处理完了过后,拓扑排序判断一下有没有环即可,若有环,则无法形成所给颜色构造,否则能形成所给颜色构造。
对于每一个块 \((x, y)\),观察图形不难发现三把刷子分别是 \(\lceil y / 2 \rceil, 2n - x + 1, 2n + x - \lfloor y / 2 \rfloor\)

点击查看代码
#include <bits/stdc++.h>
#define inf 2e18
#define int long long

void solve()
{
    int n, m;std::cin >> n >> m;
    std::vector<int> in(n * 3 + 1, 0);
    std::vector<std::vector<int>> g(n * 3 + 1);

    bool dk = true;
    for(int i = 1;i <= m;i ++) {
        int x, y, c;std::cin >> x >> y >> c;
        int p = (y - 1) / 2 + 1, q = 2 * n - x + 1, r = 2 * n + x - y / 2;
        if(!dk)continue;
        if(c != p && c != q && c != r) {
            dk = false;
            continue;
        }
        
        if(c == p) {
            g[q].push_back(c);
            g[r].push_back(c);
        } else if(c == q) {
            g[p].push_back(c);
            g[r].push_back(c);
        } else {
            g[p].push_back(c);
            g[q].push_back(c);
        }
        in[c] += 2;
    }

    if(!dk) {
        std::cout << "No\n";
        return;
    }

    auto topu = [&]() -> bool {
        std::queue<int> q;
        for(int i = 1;i <= 3 * n;i ++) {
            if(!in[i])q.push(i);
        }

        while(q.size()) {
            int now = q.front();
            q.pop();

            for(auto &i : g[now]) {
                if((-- in[i]) == 0) {
                    q.push(i);
                }
            }
        }

        for(int i = 1;i <= 3 * n;i ++) {
            if(in[i])return false;
        }

        return true;
    };

    if(topu())std::cout << "Yes\n";
    else std::cout << "No\n";
}

1007 - 学计算导致的(补题)

首先,如果没有乘法,则就是一个很简单的按照题目给出的限制的从左上到右下的路径数的 DP,维护一下每个格子求得的值的路径数。
但是此题给出了乘法运算,如果还按照一般的求路径数的 DP 来做的话,有一个无法维护的信息,那就是乘法的优先操作性,比如对于 \(3 + 2 \times 3\),应该先算 \(2 \times 3\),再加上 \(3\)
那么对于 DP 数组维护的信息,我们要有所改变。
我们注意到,无论怎么运算,一个计算式都可以写成 \(sum + mul \times cur\) 的形式,\(sum\) 表示之前计算到的和,\(cur\) 表示当前新加进来的数,\(mul\) 表示当前新加进来的数的系数,并且题目中所给的 \(k\) 最大只达到 \(20\),因此我们完全可以用 \(nmk^3\) 的空间来维护我们的 DP,\(sum, mul, cur\) 均为在 \(\mod k\) 意义下的值。
接下来考虑转移:

  • 如果新格子是一个数字 \(x\):只需要把 \(cur \rightarrow cur \times 10 + x\)
  • 如果新格子是一个乘号 *:那么 \(sum \rightarrow sum, mul \rightarrow mul \times cur, cur \rightarrow 0\)
  • 如果新格子是一个加号 \(+\):那么 \(sum \rightarrow sum + mul \times cur, mul \rightarrow 1, cur \rightarrow 0\)
  • 如果新格子是一个减号 \(-\):那么 \(sum \rightarrow sum + mul \times cur, mul \rightarrow -1, cur \rightarrow 0\)

关于转移还有一个细节,若相邻格都是符号,则不能转移,因为此时不是一个合法的计算式。
因此,我们只需要在 DP 过程中枚举前驱格子的 \(sum, mul, cur\),然后转移即可。
当然,由于 \(sum, mul, cur\) 都是在 \(\mod k\) 意义下的,因此我们可以在 \(k\) 进制意义下进行状压来对其进行编码,三个信息各占一位。
但是,如果在 DP 过程中按上述流程计算并转移,大概率会被卡常导致 TLE(因为我 TLE 了)。
我们可以发现对于每一个前置的 \(sum, mul, cur\),经过一个指定字符转移后的结果是不会变的,因此我们可以提前建立一个自动机来存储状态的转移,后续 DP 过程只需要查询转移后的结果是什么就行了,这样大大减少计算次数。
最后取的答案,就是在 \((n, m)\) 处满足值为 \(0\) 的状态的总路径数。

点击查看代码
#include <bits/stdc++.h>
#define inf 2e18

const int N = 102, M = 1e9 + 7;
int tr[150][8000];

void solve()
{
    int n, m, k;std::cin >> n >> m >> k;
    std::vector<std::vector<char>> a(n + 1, std::vector<char>(m + 1));

    for(int i = 1;i <= n;i ++) {
        for(int j = 1;j <= m;j ++) {
            std::cin >> a[i][j];
        }
    }

    std::vector<std::vector<std::array<int, 8000>>> dp(
        n + 1, std::vector<std::array<int, 8000>>(
            m + 1, std::array<int, 8000>{}));

    auto getdigit = [&](int x, int y, int z) -> int {
        return (x * k + y) * k + z;
    };

    int mx = k * k * k;
    for(int i = 0;i < k;i ++) {
        for(int j = 0;j < k;j ++) {
            for(int p = 0;p < k;p ++) {
                for(char q = '0';q <= '9';q ++) {
                    tr[q][getdigit(i, j, p)] = getdigit(i, j, (p * 10 + (q - '0')) % k);
                }
                tr['+'][getdigit(i, j, p)] = getdigit((i + j * p) % k, 1, 0);
                tr['-'][getdigit(i, j, p)] = getdigit((i + j * p) % k, k - 1, 0);
                tr['*'][getdigit(i, j, p)] = getdigit(i, (j * p) % k, 0);
            }
        }
    }

    dp[1][1][getdigit(0, 1, (a[1][1] - '0') % k)] = 1;

    for(int i = 1;i <= n;i ++) {
        for(int j = 1;j <= m;j ++) {
            for(int p = 0;p < mx;p ++) {
                if(isdigit(a[i][j]) || isdigit(a[i - 1][j]))dp[i][j][tr[a[i][j]][p]] = (1ll * dp[i][j][tr[a[i][j]][p]] + dp[i - 1][j][p]) % M;
                if(isdigit(a[i][j]) || isdigit(a[i][j - 1]))dp[i][j][tr[a[i][j]][p]] = (1ll * dp[i][j][tr[a[i][j]][p]] + dp[i][j - 1][p]) % M;
            }
        }
    }

    int ans = 0;
    
    for(int i = 0;i < k;i ++) {
        for(int j = 0;j < k;j ++) {
            for(int p = 0;p < k;p ++) {
                if((i + j * p % k) % k == 0) {
                    ans = (1ll * ans + dp[n][m][getdigit(i, j, p)]) % M;
                }
            }
        }
    }

    std::cout << ans << '\n';
}

1001 - 学位运算导致的(补题)

根据题目条件,这个集合一定是一个有限的对内进行题目运算封闭的集合。
我们对于一个 \(y\),要找到小的一个 \(x\),使得 \(x \in S\) 并且 \(x \geq y\)
我们定义一个集合中的元素 \(a\) 包含于一个集合中的元素 \(b\),当且仅当 \(a \& b = a\)
那么,我们也可以把这个推广到每一位上,对于一位 \(p\) 包含于 \(q\),当且仅当 \(p \& q = p\)
那么我们要求最小的 \(b\) 使得 \(a\) 包含于 \(b\),其实就是对于 \(a\) 的每一位 \(1\),包含 \(a\) 这一位的最小的数字,其实也就是这一位也是 \(1\) 的最小的数字,求一个按位或的值。
这个手玩一下很容易发现,因为每一位 \(1\) 都对应一个最小的,那么按位或起来也一定是最小的。
那么现在回到题目所求,对于一个不一定在集合中的元素 \(y\),我们又如何求呢?
其实也是类似的,我们存储一下每一位为 \(1\) 的最小的数记为 \(p_i\)\(i\) 是位的编号,其实也就是把这一位为 \(1\) 的所有数按位与一下,这样一定最小。
然后,对于 \(y\),我们从高位到低位贪心求解,\(tmp\) 初始为 \(0\),如果 \(y\) 这一位是 \(1\),那就 \(tmp = tmp | p_i\),否则,对 \(y\) 的答案和 \(tmp | p_i\) 取一个最小值,因为高位的 \(1,0\) 情况都相同时,此时在 \(y\)\(0\) 的位给 \(tmp\) 添加一个 \(1\)\(tmp\) 一定大于 \(y\),如此进行下去,最后还要对 \(y\) 的答案和 \(tmp\) 取一个最小值,因为这样求解后 \(tmp\) 一定大于等于 \(y\)
这样对于 \(y\) 的答案就出来了。
最后复盘一下感觉这题有点像线性基的手法,我们对于每一位找一个最小的,存储下来,求解时按位考虑,用每一位取最小来贪心得到一个总的也最小,在取答案的时候,对目标数的位为 \(0\) 和为 \(1\) 分类讨论,要么加贡献贪心求解,要么取答案保证合法。
然后注意一下这个题记得开 ull,因为题目最大的数达到了 \(2 ^ {64} - 1\)

点击查看代码
#include <bits/stdc++.h>
#define inf 2e18
#define ull unsigned long long
#define int long long

const ull M = 10'000'000'000'000'000 + 1029;
//不要用e表达,double有精度误差

void solve()
{
    int n, q;std::cin >> n >> q;
    std::vector<ull> a(n), p(64);
    
    for(auto &i : a) {
        std::cin >> i;
    }

    for(auto &i : p) {
        i = -1;
    }

    for(auto &i : a) {
        for(int j = 0;j < 64;j ++) {
            if(i & (1ull << j)) {
                p[j] = p[j] & i;
            }
        } 
    }

    ull ans = 0;
    for(int i = 1;i <= q;i ++) {
        ull x;std::cin >> x;
        ull tmp = -1;
        ull cur = 0;
        for(int j = 63;j >= 0;j --) {
            if(x & (1ull << j)) {
                cur |= p[j];
            } else {
                tmp = std::min(tmp, cur | p[j]);
            }
        }

        tmp = std::min(tmp, cur);
        ans = ans ^ (tmp % M);
    }

    std::cout << ans << '\n';
}

1010 - 学排列导致的(补题)

首先吐槽一下本题,我觉得大概率是有评测机问题的,用题目给的 swap 函数就 WA,用库里面的 swap函数就过了,挺神秘的,因为这个玩意我找了一下午加一晚上的 bug

首先不难发现,其实题目的运算就是来了个离散数学中的函数的复合运算,并且这个复合函数也是双射的(因为不管是外层函数还是内层函数都是双射的),根据复合函数的性质可以知道,函数的复合是具有结合律的,也就是说,\(f * g * h = f * (g * h) = (f * g) * h\)(这里用 \(*\) 来表示题目中的运算,下同),那么满足结合律的东西可以想到什么呢?对!线段树!
我们可以用线段树来维护区间的题目运算的值,这样就可以很快地查询了。
那么修改呢?每次修改会把一个区间内的每一个排列的以 \(z\) 为起点的后缀移动到前缀。
我们可以发现,无论如何修改,每次都只是把一个前缀和一个后缀进行交换,所以不管怎么移动,以一个数字 \(x\) 开头的排列都是固定的,而我们又可以注意到,题目给的排列元素个数 \(k\) 的范围最多才 \(30\),那么这里就有一个思路了:
我们在线段树中,同时维护 \(k\) 种信息,每一种信息表示这个区间的所有排列都以一个相同数字 \(x\) 开头后得到的这个区间按题目运算得到的排列,同时再维护一下这个区间当前的按题目运算得到的排列,在修改时,记录一下每个区间的 lazytag,在查询时,如果一个区间没有 lazytag,并且这个区间完全包含于查询区间,那么就直接返回这个区间的当前值,如果有 lazytag,并且这个区间完全包含于查询区间,那么就返回这个区间种所有排列都以 lazytag 开头进行题目运算得到的排列。
这么做确实是可行的,但是此时线段树数量过多(相当于有 \(30\) 棵线段树),常数大,稍不注意可能就会被卡常,再由于 hduoj 开的空间很小,也很容易 MLE,所以我们可以考虑更省空间的做法。
我们再看一下上面的查询方式,对于完全包含于查询区间的区间,分为一个区间有 lazytag 还是没有 lazytag,那我们为什么要把每一个数开头形成的排列塞在线段树里面用四倍空间进行存储呢?
我们定义一下题目运算的逆运算,如果 \(v * u = w\)\(w\) 为从 \(1\)\(k\) 升序的排列,那么可以得到 \(v = u^{-1}\),并且有 \(v_{u_i} = i\),那么,记 \(p_i\) 为前 \(i\) 个数进行题目运算得到的值,根据前缀和的知识,我们对于一个区间 \([l, r]\) 的运算值,就可以写成 \(p_r * p_{l - 1}^{-1}\)
那么,我们可以预处理一下以 \(1\) ~ \(k\) 的每一个数开头的所有排列构成的前缀的运算值,然后在线段树上查询时,一旦遇到一个完全包含于查询区间的区间,并且有 lazytag,就用上述算法算出这个区间的当前的运算值并返回,这样的话,我们就省下了在线段树上存储多出来的四倍空间,大大节省空间,足以通过此题。

点击查看代码
#include <bits/stdc++.h>
#define inf 2e18
#define ull unsigned long long
#define ls o << 1
#define rs o << 1 | 1

using ll = long long;
using namespace std;

const int N = 200000 + 5;

char p[N][31];
int n, m, k;

unsigned long long Seed;
unsigned myrand(){Seed^=Seed>>5;Seed^=Seed<<3;return Seed;}
template< typename T >void generate(int k,T p[]){
    auto *q=p+1;
    for(int i=1;i<=k;i++)p[i]=i;
    for(int i=k-1;i>0;i--)swap(q[i],q[myrand()%i]);
}

struct Node {
    char a[31];

    friend Node operator * (const Node &u, const Node &v) {
        Node res;
        for(int i = 1;i <= k;i ++) {
            res.a[i] = u.a[v.a[i]];
        }
        return res;
    }

    void init() {
        for(int i = 1;i <= k;i ++) {
            a[i] = i;
        }
    }

    Node shift() {
        Node res;
        for(int i = 1;i < k;i ++) {
            res.a[i] = a[i + 1];
        }
        res.a[k] = a[1];
        return res;
    }

    Node operator ! () {
        Node res;
        for(int i = 1;i <= k;i ++) {
            res.a[a[i]] = i;
        }
        return res;
    }
};

Node pre[31][N];

struct segtree {
    Node t[1 << 19];
    char lz[1 << 19];

    void build(int l, int r, int o = 1) {
        lz[o] = 0;
        if(l == r) {
            for(int i = 1;i <= k;i ++) {
                t[o].a[i] = p[l][i];
            }
            return;
        }

        int mid = l + r >> 1;
        build(l, mid, ls);
        build(mid + 1, r, rs);

        pushup(o);
    }

    void change(int l, int r, int x, int s, int e, int o = 1) {
        if(l <= s && e <= r) {
            lz[o] = x;
            t[o] = !pre[x][s - 1] * pre[x][e];
            return;
        }

        int mid = s + e >> 1;
        pushdown(o, s, mid, e);

        if(mid >= l)change(l, r, x, s, mid, ls);
        if(mid + 1 <= r)change(l, r, x, mid + 1, e, rs);

        pushup(o);
    }

    Node query(int l, int r, int s, int e, int o = 1) {
        if(lz[o]) {
            return (!pre[lz[o]][std::max(l, s) - 1]) * pre[lz[o]][std::min(r, e)];
        }

        if(l <= s && e <= r) {
            return t[o];
        }

        int mid = s + e >> 1;
        pushdown(o, s, mid, e);

        if(mid >= l && mid + 1 <= r) {
            return query(l, r, s, mid, ls) * query(l, r, mid + 1, e, rs);
        } else if(mid >= l) {
            return query(l, r, s, mid, ls);
        } else {
            return query(l, r, mid + 1, e, rs);
        }
    }

    void pushdown(int o, int s, int mid, int e) {
        if(lz[o] == 0)return;
        lz[ls] = lz[o];
        t[ls] = !pre[lz[o]][s - 1] * pre[lz[o]][mid];
        lz[rs] = lz[o];
        t[rs] = !pre[lz[o]][mid] * pre[lz[o]][e];
        lz[o] = 0;
    }

    void pushup(int o) {
        t[o] = t[ls] * t[rs];
    }
}sgt;

void solve()
{
    std::cin >> n >> m >> k >> Seed;

    for(int i = 1;i <= n;i ++) {
        generate(k, p[i]);
    } 

    std::vector<char> mp(k + 1); 
    for(int i = 0;i <= n;i ++) {
        if(i == 0) {
            for(int j = 1;j <= k;j ++) {
                pre[j][0].init();
            }
            continue;
        }

        for(int j = 1;j <= k;j ++) {
            pre[p[i][1]][i].a[j] = p[i][j];
        }

        for(int j = 2;j <= k;j ++) {
            pre[p[i][j]][i] = pre[p[i][j - 1]][i].shift();
        }

        for(int j = 1;j <= k;j ++) {
            pre[j][i] = pre[j][i - 1] * pre[j][i];
        }
    }

    sgt.build(1, n);
 
    while(m --) {
        int op, l, r, x;std::cin >> op >> l >> r >> x;
        if(op == 1) {
            std::cout << (int)sgt.query(l, r, 1, n).a[x] << '\n';
        } else {
            sgt.change(l, r, x, 1, n);
        }
    }
}
posted @ 2025-03-15 00:52  天天超方的  阅读(2088)  评论(4)    收藏  举报