• 2015多校联赛第三场(部分题解)


    1001. Magician (hdu5316)

    这个题目用到线段树来合并区间,题目有句话的意思A beautiful subsequence is a subsequence that all the adjacent pairs of elves in the sequence have a different parity of position说的是相邻的两个元素必须有不同的奇偶位置,所有一个序列有四种可能的形式,奇...奇, 奇...偶,偶...奇, 偶...偶,这四种形式只是端点的奇偶性,所以线段树的每个节点就得有四个值,分别维护这四个值就行了,举个例子来说,根节点的偶偶的最大值取决于 左孩子的偶偶 + 右孩子的奇偶, 左孩子的偶奇 + 右孩子的偶偶, 左孩子的偶偶,右孩子的偶偶, 所以根节点的偶偶就等于他们当中的最大值,同理,其他的三个也是这样,剩下的另外一种操作就是更新点了。

    #include<iostream>
    #include <stdio.h>
    using namespace std;
    typedef long long LL;
    const int maxn = 100010;
    const LL inf = 1LL << 61;
    LL d[maxn];
    struct Data{
        LL s00, s01, s10, s11;//s00表示偶偶, s01表示偶奇, s10表示奇偶,s11表示奇奇
        void init()
        {
            s00 = s01 = s10 = s11 = -inf;//初始化一定要为负无穷,因为可能有负数
        }
        LL max_ele()
        {
            return max(max(s00, s01), max(s10, s11));//找出他们当中最大的
        }
    };
    struct tree{
        Data data;
    };
    tree sum[maxn * 4];//线段树
    void checkmax(LL &a, const LL b)
    {
        if (a < b)
            a = b;
    }
    Data operator + (const Data &a, const Data &b)
    {
        Data ret;
        ret.s11 = max(a.s11 + b.s01, a.s10 + b.s11); checkmax(ret.s11, a.s11); checkmax(ret.s11, b.s11);
        ret.s10 = max(a.s11 + b.s00, a.s10 + b.s10); checkmax(ret.s10, a.s10); checkmax(ret.s10, b.s10);
        ret.s00 = max(a.s00 + b.s10, a.s01 + b.s00); checkmax(ret.s00, a.s00); checkmax(ret.s00, b.s00);
        ret.s01 = max(a.s00 + b.s11, a.s01 + b.s01); checkmax(ret.s01, a.s01); checkmax(ret.s01, b.s01);
        return ret;
    }
    void pushup(int rt)
    {
        sum[rt].data = sum[rt<<1].data + sum[rt<<1|1].data;
    }
    void build(int L, int R, int rt)
    {
        sum[rt].data.init();
        if (L == R)
        {
            if (L & 1)
                sum[rt].data.s11 = d[L];
            else
                sum[rt].data.s00 = d[L];
            return;
        }
        int mid = (L + R) / 2;
        build(L, mid, rt<<1);
        build(mid + 1, R, rt<<1|1);
        pushup(rt);
    }
    void update(int pos, LL value, int L, int R, int rt)
    {
        if (L == pos && R == pos)
        {    
            if (pos & 1)
                sum[rt].data.s11 = value;
            else
                sum[rt].data.s00 = value;
            return;
        }
        int mid = (L + R) / 2;
        if (pos <= mid)
            update(pos, value, L, mid, rt<<1);
        else
            update(pos, value, mid + 1, R, rt<<1|1);
        pushup(rt);
    }
    Data query(int l, int r, int L, int R, int rt)
    {
        if (l <= L && r >= R)
        {
            return sum[rt].data;
        }
        int mid = (L + R) >> 1;
        Data lson, rson;
        lson.init(); rson.init();
        if (l <= mid)
            lson = query(l, r, L, mid, rt<<1);
        if (r > mid)
            rson = query(l, r, mid + 1, R, rt<<1|1);
        return lson + rson;
    }
    int main()
    {
        int T, n, m;
        scanf("%d", &T);
        while (T--)
        {
    
            scanf("%d %d", &n ,&m);
            for (int i = 1; i <= n; i++)
                scanf("%I64d", &d[i]);
            build(1, n, 1);
            int op, a, b;
            for (int i = 0; i < m; i++)
            {
                scanf("%d %d %d", &op, &a, &b);
                if (op)
                    update(a, (LL)b, 1, n, 1);
                else
                {
                    Data ans = query(a, b, 1, n, 1);
                    printf("%I64d
    ", ans.max_ele());
                }
            }
        }
        return 0;
    }
    View Code

    1002.RGCDQ (hdu5317)

    这道题F(x)表示x当中素因子的个数,由于x最大1000000,所以F(x)最多不超过8个,这个可以拿出前几个素数乘一下试试,2*3*5*7*11*13*17*19=9699690>1000000所以可以先预处理出来每个数的F(x),然后求出他的前缀和,用O(1)的复杂度去查询,求F(x)的时候注意不要除着求,要直接像筛素数一样去求。具体见代码

    #include <cstdio>
    #include <string.h>
    const int maxn = 1000010;
    int prime[maxn];//标记筛选的素数
    int f[maxn];//F(x)
    int S[maxn][9];//前缀和,因为一共最多可能有8个,所以开个8的数组足够了,0的位置为空
    int pr[100000];//保存素数
    void init()
    {
        memset(prime, true, sizeof(prime));
        prime[1] = prime[0] = false;
        for (int i = 2; i * i <= 1000000; i++)//素数筛选
        {
            if (prime[i])
                for (int j = i + i; j <= 1000000; j += i)
                    prime[j] = false;
        }
        int tot = 0;
        for (int i = 2; i <= 1000000; i++)
            if (prime[i])
                pr[tot++] = i;//将筛选出来的素数放到pr中
        for (int i = 0; i < tot; i++)//求F(x),这里很关键,不能放到素数筛选的过程当中去,因为有大素数
        {
            for (int j = pr[i]; j <= 1000000; j += pr[i])//所有包含pr[i]这个素因子的都要加1
                f[j]++;
        }
        for (int i = 2; i <= 1000000; i++)
        {
            for (int j = 1; j <= 8; j++)
            {
                S[i][j] = (j == f[i]) ? S[i - 1][j] + 1 : S[i - 1][j];//求出前缀和
            }
        }
    }
    
    int main()
    {
        init();
        int T, L, R;
        scanf("%d",  &T);
        while (T--)
        {
            scanf("%d %d", &L, &R);
            int ans = 1;
            for (int i = 8; i > 0; i--)
            {
                
                if (S[R][i] - S[L - 1][i] > 1)//找出这个区间最大的来
                {
                    ans = i;
                    break;
                }
            }
            printf("%d
    ", ans);
        }
        return 0;
    }
    View Code

    1003.The Goddess Of The Moon (hdu5318)

    给出n条链子,让你选择m个,问一共方案有多少种。因为m比较大(1e9),所以用矩阵快速幂来解。构造一个矩阵表示任意两个之间是否可以链接,如果可以链接的话就是1,不能链接的话就是0,这个题有点模糊。大致知道一点意思,但是细推的话,状态转移方程不太清楚。。。看题解上写S[i][j]表示前i个,以j结尾的方案数,估计意思就是当链接i个的时候,最后以j结尾的方案数,他就应该等于前i - 1个所有能链接j,并且j链接在后面的个数,对于每一个j,对应一列,所以,就是一个矩阵,而S[i][j]可以由S[i - 1][]来推,那么就构成了矩阵连乘的条件,所以就能用矩阵快速幂了。这个题还是不是完全透彻的理解。如果哪位大牛路过,恳请指点。

    #include<iostream>
    #include <cstdio>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    using namespace std;
    typedef long long LL;
    const int maxn = 52;
    const int mod = 1000000007;
    int sz;
    struct Mat{
        int mat[maxn][maxn];
        void init()
        {
            memset(mat, 0, sizeof(mat));
        }
    };
    Mat operator * (const Mat &a, const Mat &b)//矩阵乘法
    {
        Mat ret;
        ret.init();
        for (int i = 0; i < sz; i++)
        {
            for (int j = 0; j < sz; j++)
            {
                ret.mat[i][j] = 0;
                for (int k = 0; k < sz; k++)
                {
                    ret.mat[i][j] += 1LL * a.mat[i][k] * b.mat[k][j] % mod;
                    ret.mat[i][j] %= mod;
                }
    
            }
        }
        return ret;
    }
    Mat operator ^ (Mat a, int b)//矩阵快速幂
    {
        Mat ans;
        for (int i = 0; i < sz; i++)
            for (int j = 0; j < sz; j++)
                ans.mat[i][j] = (i == j ? 1 : 0);
        while (b)
        {
            if (b & 1)
                ans = ans * a;
            a = a * a;
            b >>= 1;
        }
        return ans;
    } 
    bool connect(int a, int b)//判断a链和b链是否能连接
    {
        int dig1[maxn], dig2[maxn];
        int tot1 = 0, tot2 = 0;
        for (; a; a /= 10)
            dig1[tot1++] = a % 10;
        for (; b; b /= 10)
            dig2[tot2++] = b % 10;
        int ans = 1;
        for (int len = 1; len <= min(tot1, tot2); len++)
        {
            bool flag = false;
            for (int i = len - 1; i >= 0; i--)
            {
                if (dig1[i] != dig2[tot2 - (len - i)])
                {
                    flag = true;
                    break;
                }
            }
            if (!flag)
                ans = len;
        }
        if (ans > 1)
            return true;
        return false;
    }
    int solve(vector <int> num, int n)
    {
        vector<int> tmp = num;
        sort(tmp.begin(), tmp.end());
        tmp.erase(unique(tmp.begin(), tmp.end()), tmp.end());//去重
        int m = (int)tmp.size();
        Mat A, B;
        sz = m;
        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < m; j++)
                B.mat[i][j] = connect(tmp[i], tmp[j]);//构造转移矩阵
        }
        A.init();
        for (int i = 0; i < m; i++)
            A.mat[0][i] = 1; //初始第一行全为1
        B = B ^ (n - 1);
        A = A * B;
        int ans = 0;
        for (int i = 0; i < m; i++)
        {
            ans += A.mat[0][i];
            ans %= mod;
        }
        return ans;
    }
    int main()
    {
        int T, n, m;
        scanf("%d", &T);
        while (T--)    
        {
            scanf("%d%d", &n, &m);
            vector<int> v(n);
            int tmp;
            for (int i = 0; i < n; i++)
            {
                scanf("%d", &v[i]);
            }
            printf("%d
    ", solve(v, m));
        }
        return 0;
    }
    View Code

    1004.Painter (hdu5319)

    这个题属于模拟题,红色只能正向斜着刷(),蓝色只能反向斜着刷(/),被正向反向刷了两次的点是绿色,给出最终状态求出最少经过多少步能到这种状态。直接枚举每一个点就行。

    #include <cstdio>
    #include <cstring>
    const int maxn = 60;
    char a[maxn][maxn];
    int mp[maxn][maxn];
    int m;
    int ans;
    int n;
    const int dir[2][2] = {1, 1, 1, -1};
    void work(int x, int y, int color)
    {
        mp[x][y] -= color;
        while (true)
        {
            x += dir[color - 1][0];
            y += dir[color - 1][1];
            if (x >= 0 && y >= 0 && x < n && y < m && (mp[x][y] == color || mp[x][y] == 3)) 
                mp[x][y] -= color;
            else
                break;
        }
    }
    int main()
    {
        int T;
        scanf("%d", &T);
        while (T--)
        {
            scanf("%d", &n);
            for (int i = 0; i < n; i++)
            {
                scanf("%s", a[i]);
                m = strlen(a[i]);
                    for (int j = 0; j < m; j++)
                    {
                        if (a[i][j] == 'R')//转化成数字便于计算
                            mp[i][j] = 1;
                        else if (a[i][j] == 'B')
                            mp[i][j] = 2;
                        else if (a[i][j] == 'G')
                            mp[i][j] = 3;
                        else
                            mp[i][j] = 0;
                    }
            }
            ans = 0;
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < m; j++)
                {
                    if (mp[i][j] == 0) continue;
                    if (mp[i][j] == 1)
                        work(i, j, 1);
                    else if (mp[i][j] == 2)
                        work(i, j, 2);
                    else
                    {
                        work(i, j, 1);
                        work(i, j, 2);
                        ans++;
                    }
                    ans++;
                }
            }
            printf("%d
    ", ans);
            
        }
        return 0;
    }
    View Code

    1008.Solve this interesting problem (hdu5323)

    此题给出线段树的区间定义,给出一个节点的左区间端点和右区间端点,求最小n,其中n为根节点的右端点。

    从当前节点(L, R)往他的父节点走的话有四种路线,分别是:当他为左孩子的时候父节点的区间端点(L, R + (R - L + 1))或者(L, R + (R - L + 1) - 1),当他为右孩子的时候父节点的区间为(L - (R - L + 1), R)或者(L - (R - L + 1) - 1, R),因为线段树节点的左右孩子区间长度要不 相等,要不 左孩子比右孩子大1, 所以根据这个结论所以就可以得出上面四种方式,其中(R - L + 1)为区间长度,题目中还给了一个关键的就是L / (R - L + 1) <= 2015,这个就说明 L <= 2015 * len, 其中len为区间长度,线段树中len越小,也就是左右端点靠的越近,他的位置越深,就比如叶子节点的len为0,所以他在最下面,这就说明了一个问题就是当这个树取最深的顶点的时候,那么len取1,所以L<=2015, 并且从2015开始往上找到0的时候最多22层,当是左孩子的时候11层,右孩子11层。所以搜索这四种路径。

    #include<iostream>
    #include <cstdio>
    
    using namespace std;
    typedef long long LL;
    const LL inf = 1LL << 60;
    LL n;
    void search(LL L, LL R)
    {
        if (L < 0 || L > R)
            return;
        if (L == 0)
        {
            n = min(n, R);
            return ;
        }
        int len = R - L + 1;
        if (L < len)//剪枝,因为如果l小于区间长度的话,那么他的父节点的左端点一定为负值,所以不符合
            return;
        search(L, R + len);//左孩子
        if (len != 1)
            search(L, R + len - 1);//左孩子
        search(L - len, R);//右孩子
        search(L - len - 1, R);//右孩子
    }
    LL work(int L, int R)
    {
        n = inf;
        search(L, R);
        return n == inf ? -1 : n;
    }
    int main()
    {
    
        int L, R;
        while (~scanf("%d %d", &L, &R))
        {
            printf("%lld
    ", work(L, R));
        }
        return 0;
    }
    View Code

    1011. Work (hdu5326)

    官方签到题。直接dfs即可

    #include <cstdio>
    #include <cstring>
    #include <vector>
    using namespace std;
    const int maxn = 110;
    vector<int> mp[maxn];
    int in[maxn];
    int n, k;
    int siz[maxn];
    int dfs(int u)
    {
        siz[u] = 1;
        int len = mp[u].size();
        for (int i = 0; i < len; i++)
        {
            siz[u] += dfs(mp[u][i]);
        }
        return siz[u];
    }
    int main()
    {
        while (~scanf("%d%d", &n, &k))
        {
            memset(siz, 0, sizeof(siz));
            memset(in, 0, sizeof(in));
            for (int i = 0; i <= n; i++)
                mp[i].clear();
            int a, b;
            for (int i = 1; i < n; i++)
            {
                scanf("%d%d", &a, &b);
                mp[a].push_back(b);
                in[b]++;
            }
            int root = 0;
            for (int i = 1; i <= n; i++)
                if (!in[i])
                    root = i;
            dfs(root);
            int ans = 0;
            for (int i = 1; i <= n; i++)
                if (siz[i] - 1 == k)
                    ans++;
            printf("%d
    ", ans);
        }
        return 0;
    }
    View Code

    其它题目未补完。。。


  • 相关阅读:
    企业云盘安全性如何 怎样部署
    Sentinel 控制台部署
    nginx代理静态页面添加二级目录
    java socket
    IDEA
    golang代码生成器
    es 单节点问题
    代码模板
    错误摘要 HTTP 错误 403.14
    安装.Net Framework 4.6.2无法安装的2种情况
  • 原文地址:https://www.cnblogs.com/Howe-Young/p/4688414.html
Copyright © 2020-2023  润新知