zoukankan      html  css  js  c++  java
  • 2021杭电多校第一场 (1) 个人补题记录

    比赛链接:Here

    1001 - Mod, Or and Everything

    签到,

    打表发现与 2的次方相关联

    int main() {
        cin.tie(nullptr)->sync_with_stdio(false);
        int _; for (cin >> _; _--;) {
            ll n; cin >> n;
            if (n == 1)cout << "0
    ";
            else {
                int k = log(n) / log(2);
                ll sum = pow(2, k);
                if (sum != n)cout << sum - 1 << "
    ";
                else cout << sum / 2 - 1 << "
    ";
            }
        }
    }
    

    1002 - Rocket land

    待补

    1003 - Puzzle loop

    待补

    1004 - Another thief in a Shop

    待补

    1005 - Minimum spanning tree

    这里借用一些官方解法说明:

    对于编号为3~n的点,将所有编号为合数的点向其约数连边,编号为质数的点向2连边,不难证明这样形

    成的生成树是最小的。

    总的边权和为(质数的和 *2+合数的和),用欧拉筛预处理前缀和即可。

    效率: (mathcal{O}(n))

    const int N = 1e7 + 10;
    int cnt, vis[N], prime[N];
    
    void init() {
        for (int i = 2; i < N; i ++ ) {
            if (!vis[i]) prime[cnt ++ ] = i;
            for (int j = 0; prime[j] * i <= N; j ++ ) {
                vis[i * prime[j]] = true;
                if (i % prime[j] == 0) break;
            }
        }
    }
    int main() {
        cin.tie(nullptr)->sync_with_stdio(false);
        init();
        int _; for (cin >> _; _--;) {
            int n; cin >> n;
            ll cnt = 0;
            for (int i = 3 ; i <= n; ++i) {
                if (!vis[i])cnt += 2 * i;
                else cnt += i;
            }
            cout << cnt << "
    ";
        }
    }
    

    1006 - Xor sum (Good ,字典树)

    题意很简单:

    给定长度为 (n (le1e5)) 的序列,找到最短的连续子序列使得 异或和 不小于 (k(a_i,kle2^{30}))

    如果存在多个满足条件的序列,请输出左端点最小的连续子序列的左端点和右端点,

    如果不存在连续子序列的异或和 不小于 (k) 则输出 -1


    思路参考于 聆竹听风

    看到题目第一想法是暴力解决,但注意到 (n) 的范围是 (nle1e5) ,写BF肯定TLE了,

    由于异或满足以下性质:(sum[l...r] = sum[1...l - 1]⊕sum[1...r])

    所以我们可以用前缀和,

    (sum[1...i] = S_i) ,特别地,定义 (S_0 = 0),于是有:

    [sum[l...r] = S_{l - 1}⊕S_r ]

    从而可以将原问题转化为,在 (S_0,S_1,...,S_n) 中寻找一对在序列中最短的 (S_i,S_j(i < j)) ,满足 (S_i ⊕ S_j ge k),最终答案即为 ([i + 1,j]) ( (i+1) 是左端点,(j) 是右端点)

    正如最开始说的一样,最暴力的想法就是利用两重循环,第一层循环从 ([1,n]) 枚举 (d) ,第二层枚举 (i(0le i le n + 1 - d)) ,判断 (S_i ⊕ S_{i + d - 1} ge k) 是否成立,成立则说明答案已经找到,跳出循环,不成立则继续循环。

    // 暴力写法
    int l = -1, r = n;
    for (int i = 1; i <= n; ++i) {
        int x = 0;
        for (int j = i; j < n; ++j) {
            x ^= a[j];
            if (j - i >= r - l)break;
            if (x >= k)l = i, r = j;
        }
    }
    if (l >= 0)cout << l << " " << r << "
    ";
    else cout << "-1
    ";
    

    因为暴力做法时间复杂度为 (mathcal{O}(n^2)),会超时,所以需要优化。不妨考虑枚举右侧端点 (i) ,尝试是否能在区间 ([0,i - 1]) 上快速找到离 (i) 最近的 (j) 使得 (S_i ⊕ S_j ge k)

    既然是异或问题,一定和二进制相关,而题目给出的范围是 (0le a_ile 2^{30}) ,所以 (S_i) 也在这个范围中,说明 (S_i) 可以用 (30) 位二进制表示,于是 (S_i) 可以看成长度为 (30) 的 "01"字符串。

    故而可以考虑在枚举 (i) 的时候,动态地维护一个包含 (S_0,S_1,...,S_{i−1}) 的"01"字典树,其中深度小的节点存储高位,深度大的节点存储低位。字典树的每个节点附加存储着这个节点所表示的前缀(从高位开始的"01"串)最后一次在数列 (S_0,S_1,...,S_{i−1}) 中出现的位置,没出现过位置就记成 (−1)

    然后让 (S_i) 和字典树中的串进行带剪枝的逐位异或:

    为了方便描述,记 (S_i) 中从高位到低位数起第 (j) 位(以下简称“第 (j) 位”)为 (S_{ij})(k) 中第 (j) 位是 (k_j)

    假设目前正在考虑第 (j) 位的情况,即到达了字典树的第 (j−1) 层(根节点为空前缀,把它当成第 (0) 层),考虑往哪个方向上的子树深入下去(并不是两个方向上都需要深入,即剪枝)。

    1. (k_j=1) ,就要求字典树中所表示串的(j)(S_{ij}) 异或的结果也是 (1) ,才有可能使得最终异或结果大于等于 (k),由于(S_{ij}⊕1)(S_{ij}) 异或结果是 (1),故此时需要往 (S_{ij}⊕1) 方向的那个子树上深入。
    2. (k_j=0) ,说明字典树中所表示串的(j)(S_{ij})异或的结果可以是 (0),也可以是 (1)
      • 若字典树中所表示串的(j)(S_{ij}) 异或的结果是 (1) ,由于(S_{ij}⊕1)(S_{ij}) 异或结果是 (1),即考虑往 (S_{ij}⊕1) 方向,发现此时无需进行后续位的异或,也可知道最终异或结果大于(k),故无需往 (S_{ij}⊕1) 方向的子树下深入,直接利用往 (S_{ij}⊕1) 方向的节点所附加的信息更新答案(别忘了,每个节点附加记录了这个节点所代表前缀最后一次在数列 (S_0,S_1,...,S_{i−1}) 中出现的位置,这是一种剪枝,也是优化的关键)。
      • 若字典树中所表示串的 (j)(S_{ij}) 异或 的结果是 (0) ,由于 (S_{ij})(1) 异或结果是 (0) ,即考虑往(S_{ij})方向,此时还需要进行后续位的异或,故需要往 (S_{ij}) 方向的子树上深入。

    假如逐位异或能够进行到最后一位,那说明异或到最后才比较出大于等于 (k),此时直接利用叶节点附加信息更新答案。

    (S_i) 与字典树中的串进行带剪枝的逐位异或之后,就需要把 (S_i) 这个串插入到字典树中,注意插入过程需要更新节点的附加信息,以便后续计算。

    时间复杂度分析:由于字典树只会往一个方向遍历,设整数序列最大的数为 (P) (最大为 (2^{30})),则树的最大深度是(log⁡P),整数序列长度为 (n),故复杂度为 (O(nlogP)),本题中可以认为是 (O(30n))

    【AC Code】

    const int N = 1e5 + 10, M = 3e6 + 10;
    int a[N];
    int ch[M][2], val[M];
    
    int main() {
        cin.tie(nullptr)->sync_with_stdio(false);
        int _; for (cin >> _; _--;) {
            int n, k, tot = 1;
            cin >> n >> k;
            a[0] = 0;
            for (int i = 1; i <= n; ++i) {
                cin >> a[i];
                a[i] ^= a[i - 1];
            }
    
            ch[1][0] = ch[1][1] = 0;
            val[1] = 1;
            int l = -1, r = n + 1;
            for (int i = 0; i <= n; ++i) {
                int now = 1, ans = -1;
                for (int j = 29; j >= 0; --j) {
                    int dig = (a[i] >> j) & 1;
                    if ((k >> j) & 1)           // k的当前位为1,只能和dig异或结果为1,才可能大于等于k
                        now = ch[now][dig ^ 1]; // 与dig异或结果为1的数是dig^1
                    else {                      // k的当前位为0,和dig异或结果可以是1也可以是0
                        if (ch[now][dig ^ 1])   // 和dig异或结果为1,后面的位都无须看,结果一定大于k
                            ans = max(ans, val[ch[now][dig ^ 1]]);
                        // 和dig异或结果是1的情况就无须遍历,只需要遍历和dig异或结果为0的情况
                        now = ch[now][dig];
                    }
                    // 节点没了
                    if (now == 0) break;
                }
    
                if (now) ans = max(ans, val[now]);
                // 更新当前最小区间序列
                if (ans >= 0 and i - ans < r - l) {
                    l = ans, r = i;
                }
                now = 1;
                for (int j = 29; j >= 0; --j) {
                    int dig = (a[i] >> j) & 1;
                    if (!ch[now][dig]) {
                        ch[now][dig] = ++tot;
                        ch[tot][0] = ch[tot][1] = 0;
                        val[tot] = -1;
                    }
                    now = ch[now][dig];
                    val[now] = max(val[now], i);
                }
            }
    
            if (l == -1 and r == n + 1) cout << "-1
    ";
            else cout << l + 1 << " " << r << "
    ";
        }
    }
    

    顺便贴一下官方解释:

    对数列做前缀异或,将题面转化为找两个距离最近的数,使得他们的异或值不小于 (k)

    枚举靠右的那个数,同时维护字母树,字母树每个节点保存范围内最靠右的点的位置。根据k来询

    问对应的 (log) 个节点,从而更新答案。

    效率: (O(nlogn))

    1007 - Pass!

    待补

    1008 - Maximal submatrix (Good)

    题意:

    给定一个数字矩阵,求出最大面积的满足每列非递减的矩阵


    一开始dp写的,但发现TLE了,然后转念想到这完全可以用单调栈解决。在思考的同时队友WJX大牛已经AC了,所以比赛的时候就没往下想。赛后补一下

    本题正解为原矩阵转为01矩阵,再使用悬线法求最大01矩阵即可

    01 矩阵:1 代表该位置是否比前一位小

    悬线法算法讲解:Here

    复杂度 (O(n^2))

    const int N = 2e3 + 10;
    // a为原矩阵,b转为01矩阵
    int a[N][N], b[N][N];
    int H[N], Q[N];
    
    int main() {
        cin.tie(nullptr)->sync_with_stdio(false);
        int _; for (cin >> _; _--;) {
            int n, m;
            cin >> n >> m;
            for (int i = 1; i <= n; ++i)
                for (int j = 1; j <= m; ++j) {
                    cin >> a[i][j];
                    b[i][j] = 0; // init
                    if (i > 1)b[i][j] = (a[i][j] >= a[i - 1][j]);
                }
    
            for (int i = 1; i <= m; ++i) H[i] = 0;
    
            int ans = 0;
            for (int i = 1; i <= n; ++i) {
                for (int j = 1; j <= m; ++j) {
                    if (b[i][j] == 0) H[j] = 1;
                    else H[j]++;
                }
                int cnt = 0;
                H[m + 1] = 0;
                for (int j = 1; j <= m + 1; ++j) {
                    while (cnt and H[Q[cnt]] > H[j]) {
                        ans = max(ans, (j - Q[cnt - 1] - 1) * H[Q[cnt]]);
                        --cnt;
                    }
                    Q[++cnt] = j;
                }
            }
            cout << ans << '
    ';
        }
    }
    

    在贴一下 WJX 大牛的单调栈写法:

    const int N = 2010;
    int c[N][N];
    int n, m, h[N][N];
    
    LL work(int h[N], int n) {
        int l[N], r[N], q[N];
        h[0] = h[n + 1] = -1;
        int tt = 0;
        q[0] = 0;
        for (int i = 1; i <= n; i++) {
            while (h[i] <= h[q[tt]]) tt--;
            l[i] = q[tt];
            q[++tt] = i;
        }
        tt = 0;
        q[0] = n + 1;
        for (int i = n; i >= 1; i--) {
            while (h[i] <= h[q[tt]]) tt--;
            r[i] = q[tt];
            q[++tt] = i;
        }
        LL ans = 0;
        for (int i = 1; i <= n; i++) {
            ans = max(ans, (LL)h[i] * (r[i] - l[i] - 1));
        }
        return ans;
    }
    
    int main() {
        ios::sync_with_stdio(false);
        int t;
        cin >> t;
        while (t--) {
            memset(c, 0, sizeof(c));
            memset(h, 0, sizeof(h));
            cin >> n >> m;
            for (int i = 1; i <= n; i++)
                for (int j = 1; j <= m; j++)
                    cin >> c[i][j];
            for (int i = 1; i <= n; i++)
                for (int j = 1; j <= m; j++)
                    if (c[i][j] >= c[i - 1][j]) h[i][j] = h[i - 1][j] + 1;
                    else h[i][j] = 1;
            LL ans = 0;
            for (int i = 1; i <= n; i++) {
                ans = max(ans, work(h[i], m));
            }
            cout << ans << endl;
        }
        return 0;
    }
    

    1009 -KD-Graph (Good)

    思维+并查集,签到(雾

    题意都能看懂就不放了


    将边按权值从小到大排序,每一阶段取出同权值的所有边,将这些边的端点用并查集两两合并,若某一阶段的全部边合并完,并查集数量为k,则当前阶段合并边的权值就是答案,否则输出-1。

    const int N = 1e6 + 10;
    struct node {int u, v, w;} q[N];
    int f[N];
    int find(int x) {return f[x] == x ? x : f[x] = find(f[x]);}
    bool cmp(node a, node b) {return a.w < b.w;}
    int main() {
        cin.tie(nullptr)->sync_with_stdio(false);
        int _; for (cin >> _; _--;) {
            int n, m, k;
            cin >> n >> m >> k;
            int now = n, ans = 0;
            for (int i = 1; i <= n; i++) f[i] = i;
            for (int i = 1; i <= m; i++) cin >> q[i].u >> q[i].v >> q[i].w;
            sort(q + 1, q + 1 + n, cmp);
            for (int i = 1 ; i <= m; ++i) {
                if (q[i].w != q[i - 1].w) {if (now == k) break;}
                if (find(q[i].u) == find(q[i].v))continue;
                now --, f[find(q[i].u)] = find(q[i].v), ans = q[i].w;
            }
            cout << (now == k ? ans : - 1) << "
    ";
        }
    }
    

    1010 - zoto

    待补

    1011 - Necklace of Beads

    看完题目感觉能做,但证明写了半个小时硬是没想出来,淦,太菜了

    这里贴一下正解证明过程

    1011-证明1

    1011-证明2

    【AC Code】

    using ll = long long;
    const int N = 1000001, mod = 998244353;
    ll n, k, ans, fac[N], inv[N], invfac[N], f[N], p[N], vis[N], prime[N], cnt, phi[N];
    void init() {
        phi[1] = p[0] = fac[0] = invfac[0] = inv[1] = fac[1] = invfac[1] = 1;
        p[1] = 2;
        for (ll i = 2; i < N; i++) {
            if (!vis[i])prime[++cnt] = i, phi[i] = i - 1;
            for (ll j = 1; j <= cnt && i * prime[j] < N; j++) {
                vis[i * prime[j]] = 1;
                if (i % prime[j] == 0) {
                    phi[i * prime[j]] = phi[i] * prime[j];
                    break;
                }
                phi[i * prime[j]] = phi[i] * (prime[j] - 1);
            }
            p[i] = p[i - 1] * 2ll % mod;
            fac[i] = fac[i - 1] * i % mod;
            inv[i] = (mod - mod / i) * inv[mod % i] % mod;
            invfac[i] = invfac[i - 1] * inv[i] % mod;
        }
    }
    
    ll C(ll n, ll m) {
        if (n < 0 || m < 0 || m > n)return 0;
        return fac[n] * invfac[m] % mod * invfac[n - m] % mod;
    }
    
    ll get(ll n, ll k) {
        f[0] = n & 1 ? 0 : 2;
        for (ll m = 1; m <= n; m++)
            f[m] = (p[m] * (C(n - m, m) + C(n - m - 1, m - 1)) + f[m - 1]) % mod;
        return f[min(n, k)];
    }
    
    int main() {
        cin.tie(nullptr)->sync_with_stdio(false);
        init();
        int _; for (cin >> _; _--;) {
            ans = 0;
            cin >> n >> k;
            for (ll d = n; d >= 1; d--)
                if (n % d == 0)
                    ans = (ans + phi[n / d] * get(d, k * d / n)) % mod;
            cout << ans *inv[n] % mod << "
    ";
        }
    }
    

    The desire of his soul is the prophecy of his fate
    你灵魂的欲望,是你命运的先知。

  • 相关阅读:
    jar 反编译工具
    SpringBoot 中注解方式的拦截过滤
    jetty 启动项目在pom.xml 的配置
    java Exception 处理汇总
    mysql-覆盖索引
    程序员为何如此累
    启动centos 不带桌面
    Linux 和 Vim 常用命令整理
    How to Use tomcat on Linux
    Mac Book 问题汇集
  • 原文地址:https://www.cnblogs.com/RioTian/p/15040229.html
Copyright © 2011-2022 走看看