• 2021牛客寒假算法基础集训营4


    B 武辰延的字符串 || 字符串哈希+二分

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    const int maxn = 1e5 + 5;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    const int P = 131;
    const double pi = acos(-1.0);
    char s[maxn], t[maxn];
    ull p[maxn], hs[maxn], ht[maxn];
    ll ans;
    ull getHash(int l, int r, ull h[])
    {
        return h[r] - h[l-1] * p[r-l+1];
    }
    int main()
    {
        cin >> s + 1 >> t + 1;
        int n = strlen(s + 1), m = strlen(t + 1);
        p[0] = 1;
        for(int i = 1; i < maxn; ++i) p[i] = p[i-1] * P;
        for(int i = 1; i <= n; ++i) hs[i] = hs[i-1] * P + s[i];
        for(int i = 1; i <= m; ++i) ht[i] = ht[i-1] * P + t[i];
        for(int i = 1; i < m; ++i)
        {
            if(s[i] != t[i]) break;
            int l = 1, r = min(n, m - i);
            if(s[1] != t[l+i]) continue;
            if(getHash(1, r, hs) == getHash(l + i, r + i, ht)) {
                ans += r;
                continue;
            }
            while(r - l > 1)
            {
                int mid = (r + l) / 2;
                if(getHash(1, mid, hs) == getHash(i + 1, mid + i, ht)) l = mid;
                else r = mid;
            }
            ans += l;
        }
        cout << ans << endl;
    }
    

    G 九峰与蛇形填数

    比赛时T了,n <= 2000,m <= 3000
    比赛时想到了倒着处理,但是循环的顺序没有想出来。每个位置的最终状态取决于最后一次修改它时的结果。我们可以先遍历每个位置,再对每个位置遍历m次操作,如果该位置位于该次操作范围内,就计算出结果并填入,然后跳出循环。这样就能保证每个位置最多只处理一次,复杂度上限是2000 * 2000 * 3000 = 1e10,但是跑不满,因为不可能每个位置都需要遍历m次操作。
    还有一个处理就是,记录下来m次操作的过程中,找出修改过的最大范围,这样在之后遍历位置的时候,从来没有被修改过的数就无需遍历操作。

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    const int maxn = 2022;
    const int maxm = 3033;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    const int P = 131;
    const double pi = acos(-1.0);
    int x[maxm], y[maxm], k[maxm];
    int a[maxn][maxn];
    int main()
    {
        int n, m, tmp;
        int xmin = INF, ymin = INF, xmax = 0, ymax = 0;
        cin >> n >> m;
        for(int i = 1; i <= m; ++i)
        {
            scanf("%d %d %d", &x[i], &y[i], &k[i]);
            xmin = min(xmin, x[i]);
            ymin = min(ymin, y[i]);
            xmax = max(xmax, x[i] + k[i] - 1);
            ymax = max(ymax, y[i] + k[i] - 1);
        }
        for(int i = 1; i <= n; ++i)
        {
            for(int j = 1; j <= n; ++j)
            {
                if(i < xmin || i > xmax || j < ymin || j > ymax) continue;
                for(int h = m; h >= 1; --h)
                {
                    if(i >= x[h] && i <= x[h] + k[h] - 1 && j >= y[h] && j <= y[h] + k[h] - 1)
                    {
                        tmp = (i - x[h]) * k[h];
                        if((i - x[h]) % 2 == 0) a[i][j] = tmp + j - y[h] + 1;
                        else a[i][j] = tmp + k[h] + y[h] - j;
                        break;
                    }
                }
            }
        }
        for(int i = 1; i <= n; ++i)
        {
            for(int j = 1; j <= n; ++j)
                printf("%d ", a[i][j]);
            printf("
    ");
        }
    }
    

    F 魏迟燕的自走棋 || 并查集

    建图思想:并查集维护基环树

    图的问题:从联通块开始入手分析

    注意到 (k) 只能取0、1,一个装备只能分给一个或两个人,想到转化到图
    首先我们考虑把 (m) 件装备看作 (m) 条连接两个点的边(如果 (k=1) 则当成自环)。于是我们的问题转化为选出一张边权和最大的生成子图。
    这个图是由0个或若干个联通块构成。对于每一个联通块,它至少是一棵树,保证其联通。如果是一棵树的话,它表示每个人可以分到一个和他相连的装备,然后会多出一个点。此时在这个树的基础上多一条边,那么就变成了一棵基环树,基环树表示每个人都正好可以分到一个装备。此时如果再加一条边,那么就会出现装备没人使用,而我们要求最多这些装备能提供多大战斗力,所以这样的边就没用了。由此可以看出,每个联通块要么是树,要么是基环树。

    一个结论是每次都选最大边一定不劣。这个结论来自kruskal算法的推广。

    一旦我们选择一条最大边后,我们把两个点缩成一个点(选中非自环的情况),或者直接把这个点丢掉(选中自环的情况),就面临一个规模更小且完全一致的问题,可以继续执行选取最大边的策略。

    也就是说我们只需要按照边权从大到小的顺序贪心地拿边,用一个并查集维护联通性和是否带环即可。

    证明:

    考虑一个不取最大边的方案。

    如果把最大边加入后生成子图依旧满足条件,那么加入这条最大边会更优。

    如果加入后发现这条边所在的连通块的边数超过了点数,说明原方案已经构成一棵基环树或两棵的基环树(被最大边连接),那么我们肯定可以断掉一条环边,把它变成一棵树或者一棵树+一棵基环树,再把最大边加入使其变成一棵基环树。

    由于删掉的边的权值一定不大于最大边,因此新方案一定不劣于原方案。

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    const int maxn = 1e5 + 7;
    const int maxm = 3033;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    const int P = 131;
    const double pi = acos(-1.0);
    struct node
    {
        int x, y, w;
    }pro[maxn];
    
    bool cmp(const node& a, const node& b)
    {
        return a.w > b.w;
    }
    
    int f[maxn];
    bool mark[maxn];
    ll ans;
    
    void init(int n)
    {
        for(int i = 1; i <= n; ++i)
            f[i] = i;
    }
    
    int find(int x)
    {
        if(f[x] == x) return x;
        return f[x] = find(f[x]);
    }
    
    int main()
    {
        int n, m, k;
        cin >> n >> m;
        for(int i = 1; i <= m; ++i)
        {
            scanf("%d %d", &k, &pro[i].x);
            if(k == 2) scanf("%d", &pro[i].y);
            else pro[i].y = INF;
            scanf("%d", &pro[i].w);
        }
        sort(pro + 1, pro + 1 + m, cmp);
        init(n);
        for(int i = 1; i <= m; ++i)
        {
            if(pro[i].y == INF)
            {
                int fx = find(pro[i].x);
                if(!mark[fx])
                {
                    ans += pro[i].w;
                    mark[fx] = 1;
                }
            }
            else
            {
                int fx = find(pro[i].x), fy = find(pro[i].y);
                if(fx == fy) // 一棵树自己和自己合并,变成基环树
                {
                    if(mark[fx]) continue;
                    mark[fx] = 1;
                    ans +=pro[i].w;
                    continue;
                }
                if(mark[fx] && mark[fy]) continue;
                ans += pro[i].w;
                f[fx] = fy;
                if(mark[fx] || mark[fy]) mark[fy] = 1;
            }
        }
        printf("%lld
    ", ans);
    }
    

    H 吴楚月的表达式 || dfs

    一个非空表达式前缀可以表示成 (a+b) 的形式。
    如果后面接了一个 (+x) ,则变成 ((a+b)+x)
    如果后面接了一个 (-x) ,则变成 ((a+b)+(-x))
    如果后面接了一个 (*x) ,则变成 $a+b∗x $;
    如果后面接了一个 (/x) ,则变成 $a+b/x $;
    最后还是可以表示成 (a+b) 的形式。
    因此只需要遍历整棵树维护每个节点对应的 (a+b) 即可,分别用q1[x],q2[x]表示。
    注意:除法取模需要逆元:限制 mod 必须为质数。a/b % mod == a * bmod-2 (模意义下)

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    const int maxn = 1e5 + 7;
    const int maxm = 3033;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    const int P = 131;
    const double pi = acos(-1.0);
    ll a[maxn];
    ll q1[maxn], q2[maxn];
    vector<int>v[maxn];
    char op[maxn];
    ll qpow(ll a, ll b)
    {
        ll t = 1;
        for(; b; b >>= 1, a = a * a % mod)
            if(b & 1)  t = t * a % mod;
        return t;
    }
    void dfs(int x)
    {
        for(auto y : v[x])
        {
            if(op[y] == '+'){
                q1[y] = (q1[x] + q2[x]) % mod;
                q2[y] = a[y];
            }else if(op[y] == '-'){
                q1[y] = (q1[x] + q2[x]) % mod;
                q2[y] = -a[y] + mod;
            }else if(op[y] == '*'){
                q1[y] = q1[x];
                q2[y] = q2[x] * a[y] % mod;
            }else if(op[y] == '/'){
                q1[y] = q1[x];
                q2[y] = q2[x] * qpow(a[y], mod - 2) % mod;
            }
            dfs(y);
        }
    }
    
    int main()
    {
        int n, x;
        cin >> n;
        for(int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
        for(int i = 2; i <= n; ++i)
        {
            scanf("%d", &x);
            v[x].push_back(i);
        }
        scanf("%s", op + 2);
        q1[1] = 0, q2[1] = a[1];
        dfs(1);
        for(int i = 1; i <= n; ++i)
            printf("%lld ", (q1[i] + q2[i]) % mod);
    }
    

    官方题解

  • 相关阅读:
    Xpath定位总结
    robotframework运行时后台报错UnicodeDecodeError
    Selenium驱动Microsoft Edge浏览器(基于robotframework框架)的方法
    robotframework自动化测试安装配置
    硬币
    矩阵乘法
    动态规划和凸性优化
    动态规划背包问题--做题小总结
    CSAPP实验attacklab
    信息学奥赛出局?教育部:若提出申请,会认真研究
  • 原文地址:https://www.cnblogs.com/Maxx-el/p/14419434.html
Copyright © 2020-2023  润新知