zoukankan      html  css  js  c++  java
  • 20200725模拟赛5题解

    A. 走廊泼水节

    题目描述

    • 话说,中中带领的 OIER 们打算举行一次冬季泼水节,当然这是要秘密进行的,绝对不可以让中中知道。不过中中可是老江湖了,当然很快就发现了我们的小阴谋,于是他准备好水枪迫不及待的想要加入我们了。
    • 我们一共有 n 个 OIER 打算参加这个泼水节,同时很凑巧的是正好有 n 个水龙头(至于为什么,我不解释)。n 个水龙头之间正好有 n-1 条小道,并且每个水龙头都可以经过小道到达其他水龙头(这是一棵树,你应该懂的..)。但是 OIER 门为了迎接中中的挑战,决定修建一些个道路(至于怎么修,秘密),使得每个水龙头到每个水龙头之间都有一条直接的道路连接(也就是构成一个完全图呗)。但是 OIER 门很懒得,并且记性也不好,他们只会去走那 n-1 条小道,并且希望所有水龙头之间修建的道路,都要大于两个水龙头之前连接的所有小道(小道当然要是最短的了)。所以神 COW 们,帮那些 OIER 们计算一下吧,修建的那些道路总长度最短是多少,毕竟修建道路是要破费的~~

    输入格式

    • 第一行 t,表示有 t 组测试数据
    • 对于每组数据,第一行 n,表示水龙头的个数(当然也是 OIER 的个数);
    • 2 到 n 行,每行三个整数 X,Y,Z;表示水龙头X和水龙头Y有一条长度为Z的小道

    输出格式

    • 对于每组数据,输出一个整数,表示修建的所有道路总长度的最短值。

    样例输入

    2 
    3 
    1 2 2 
    1 3 3 
    4 
    1 2 3 
    2 3 4 
    3 4 5
    

    样例输出

    4
    17
    

    样例解释

    • 第一组数据,在2和3之间修建一条长度为4的道路,是这棵树变成一个完全图,且原来的树依然是这个图的唯一最小生成树.

    数据范围与提示

    • 每个测试点最多 10 组测试数据;
    • 50% : (nle 1500)
    • 100% : (nle 6000,1le zle 100)

    Solve

    • 根据题意,题目中给出来的这个图是完全图的最小生成树,我们就按照做最小生成树的方式模拟,对给出的边从小到大排序,用并查集维护,依次合并最小边的两端点,同时在两端点所属集合进行连线,需连(siz_x imes siz_y - 1)条边(已经连了一条了),边权就是这条边的边权加 1 。

    Code

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int N = 6005;
    struct Node {
        int x, y, d;
        bool operator < (const Node &b) const {
            return d < b.d;
        }
    }e[N];
    int T, n, f[N], s[N], ans;
    int Find(int x) {
        return x == f[x] ? x : (f[x] = Find(f[x]));
    }
    int main() {
        scanf("%d", &T);
        while (T--) {
            ans = 0;
            scanf("%d", &n);
            for (int i = 1; i <= n; ++i)
                f[i] = i, s[i] = 1;
            for (int i = 1; i < n; i++)
                scanf("%d%d%d", &e[i].x, &e[i].y, &e[i].d);
            sort(e + 1, e + n);
            for (int i = 1; i < n; ++i) {
                int x = Find(e[i].x), y = Find(e[i].y);
                ans += (s[x] * s[y] - 1) * (e[i].d + 1);
                f[x] = y;
                s[y] += s[x];
            }
            printf("%d
    ", ans);
        }
        return 0;
    }
    

    B. Max Flow

    题目描述

    • 农场主约翰在他的N (2≤N≤50,000)个牛棚间安装了N-1条管道去运输牛奶,每个管道连接两个牛棚,每个牛棚都用管道直接或间接连接。
    • 约翰在K(1≤K≤100,000)对牛棚间挤奶。假设约翰在第i对牛棚间挤奶,两牛棚分别是si,ti,这样挤出的牛奶将会经过si到ti间(包括端点)的每一个牛棚一次,在约翰挤完K对牛棚间的牛奶后,请问运输过牛奶次数最多的牛棚的运送次数是多少?

    输入格式

    • 第一行有两个整数N和K.
    • 接下来N-1行每行两个整数x和y(x≠y)表示牛棚x和y之间有一个管道。接下来K行,每行两个整数s,t表示约翰要在牛棚s和t挤奶。

    输出格式

    • 只有一行,为K次操作完毕后权值最大的那个点的权值。

    样例输入

    5 10
    3 4
    1 5
    4 2
    5 4
    5 4
    5 4
    3 5
    4 3
    4 3
    1 3
    3 5
    5 4
    1 5
    3 4
    

    样例输出

    9
    

    Solve

    • 树上差分板子题,从 x 到 y 的路径上每个点加 1,就是
      ++w[x]; ++w[y]; --w[lca]; --w[fa[lca]];
      最后统计一下树上前缀和就是每个点的点权
    • 为什么是--w[lca]; --w[fa[lca]]呢?正常来讲,[l, r]区间加1 是 ++w[l], --w[r+1],类比一下可知树上应该是w[fa[lca]]-=2 可是这样以来计算他们的lca的时候就多算了一次,所以是--w[lca]; --w[fa[lca]],保证对lca加1,对fa[lca]没影响。

    Code

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int N = 5e4 + 5;
    struct Side {
        int t, next;
    }e[N<<1];
    int head[N], tot;
    void Add(int x, int y) {
        e[++tot] = (Side) {y, head[x]};
        head[x] = tot;
    }
    int n, k, d[N], f[N][21], w[N], ans;
    void Dfs1(int x) {//Lca预处理
        d[x] = d[f[x][0]] + 1;
        for (int i = head[x]; i; i = e[i].next) {
            int y = e[i].t;
            if (y == f[x][0]) continue;
            f[y][0] = x;
            Dfs1(y);
        }
    }
    int Jump(int x, int k) {
        int a = 0;
        while (k) {
            if (k & 1) x = f[x][a];
            k >>= 1; ++a;
        }
        return x;
    }
    int Lca(int x, int y) {
        if (d[x] < d[y]) swap(x, y);
        x = Jump(x, d[x] - d[y]);
        if (x == y) return x;
        for (int i = 20; i >= 0; --i)
            if (f[x][i] != f[y][i])
                x = f[x][i], y = f[y][i];
        return f[x][0];
    }
    void Dfs2(int x) {
        for (int i = head[x]; i; i = e[i].next) {
            int y = e[i].t; 
            if (y == f[x][0]) continue;
            Dfs2(y);
            w[x] += w[y];
        }
        ans = max(ans, w[x]);
    }
    int main() {
        scanf("%d%d", &n, &k);
        for (int i = 1; i < n; ++i) {
            int x, y;
            scanf("%d%d", &x, &y);
            Add(x, y); Add(y, x);
        }
        Dfs1(1);
        for (int i = 1; i <= 20; ++i)
            for (int x = 1; x <= n; ++x)
                f[x][i] = f[f[x][i-1]][i-1];
        while (k--) {
            int x, y;
            scanf("%d%d", &x, &y);
            int lca = Lca(x, y);
            ++w[x]; ++w[y]; --w[lca]; --w[f[lca][0]];
        }
        Dfs2(1);
        printf("%d
    ", ans);
        return 0;
    }
    

    C. Monotonicity 2(数据加强版)

    题目描述

    • 对于一个整数序列(a_1,a_2,...,a_n),我们定义其“单调序列"为一个由 <,> 和 = 组成的符号序列 (s_1,s_1,...s_{n-1}),其中符号 (s_i) 表示 (a_i)(a_{i+1}) 之间的关系。例如,数列 2,4,3,3,5,3 的单调序列为 <,>,=,<,> 。
    • 对于整数序列 (b_1,b_2,...,b_{n+1}) 以及其单调序列 (s_1,s_2,..s_n),如果符号序列 (s'_1,s'_2,...s'_k) 满足对所有 (1le ile n)(s_i=s'_{((i-1)mod k)+1}),我们就说序列 (s_1,s_2,...,s_n)「实现」了序列 (s'_1,s'_2,...,s'_k)。也就是说,序列 (s_1,s_1,...s_n) 可以通过重复多次 (s'_1,s'_2,...,s'_k) 序列并删除一个后缀得到。例如,整数数列 2,4,3,3,5,3 至少实现了以下符号序列:
      • <,>,=
      • <,>,=,<,>
      • <,>,=,<,>,<,<,=
      • <,>,=,<,>,=,>,>
    • 给定一个整数序列 (a_1,a_2,...,a_n) 以及一个单调序列 (s_1,s_2,..s_k),求出原整数序列最长的子序列(a_{i_1},a_{i_2},...,a_{i_m}(1le i_1<i_2<...<i_mle n)) 使得前者的单调序列实现后者的符号序列。

    输入格式

    • 第一行包含用空格分隔的两个整数 n,k,分别表示整数序列 (a_i) 的长度和单调序列 (s_j) 的长度。
    • 第二行包含用空格分隔的 n 个整数,表示序列 (a_i).
    • 第三行包含用空格分隔的 k 个符号,表示符号序列 (s_j).

    输出格式

    • 第一行输出一个整数 m (保证答案不小于 2),表示序列 (a_1,a_2,...,a_n) 的最长的「实现」了单调序列 (s_1,s_2,..s_n) 的子序列。
    • 第二行输出任意一个这样的子序列(a_{i_1},a_{i_2},...,a_{i_m}),元素之间用空格分隔。

    样例输入

    7 3
    2 4 3 1 3 5 3
    < > =
    

    样例输出

    6
    2 4 3 3 5 3
    

    数据范围与提示

    • 对于 100% 的数据(1le 5 imes 10^5,1le kle 100,1le a_i le 10^6,s_jin {<,>,=})

    Solve

    Code

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int N = 5e5 + 5, M = 1e6 + 5;
    char c[N];
    int n, k, a[N], f[N], t1[M], t2[M], t[M], p[N], ans, last;
    int low(int x) {//lowbit函数
        return x & -x;
    }
    void Change1(int x) {前缀最大值向后更新
        for (int i = a[x]; i <= 1e6; i += low(i))
            if (f[x] > f[t1[i]]) t1[i] = x;
    }
    int Ask1(int x) {前缀最大值向前查询
        int b = 0;
        for (int i = a[x] - 1; i; i -= low(i))
            if (f[b] < f[t1[i]]) b = t1[i];
        return b;
    }
    void Change2(int x) {后缀最大值向前更新
        for (int i = a[x]; i; i -= low(i))
            if (f[x] > f[t2[i]]) t2[i] = x;
    }
    int Ask2(int x) {后缀最大值向后查询
        int b = 0;
        for (int i = a[x] + 1; i <= 1e6; i += low(i))
            if (f[b] < f[t2[i]]) b = t2[i];
        return b;
    }
    void Print(int x) {//递归输出方案
        if (!x) return;
        Print(p[x]);
        printf("%d ", a[x]);
    }
    int main() {
        scanf("%d%d", &n, &k);
        for (int i = 1; i <= n; ++i)
             scanf("%d", &a[i]), f[i] = 1;//f初始化为1
        for (int i = 1; i <= k; ++i)
            scanf(" %c", &c[i]);
        for (int i = k + 1; i < n; ++i)
            c[i] = c[(i-1)%k+1];//展开
        for (int i = 1, j; i <= n; ++i) {
            if (f[i] < f[j=Ask1(i)] + 1)//查询小于号
                f[i] = f[j] + 1, p[i] = j;
            if (f[i] < f[j=Ask2(i)] + 1)//查询大于号
                f[i] = f[j] + 1, p[i] = j;
            if (f[i] < f[j=t[a[i]]] + 1)//查询等于号
                f[i] = f[j] + 1, p[i] = j;
            if (ans < f[i]) ans = f[i], last = i;//更新答案
            if (c[f[i]] == '<') Change1(i);//更新小于号
            if (c[f[i]] == '>') Change2(i);//更新大于号
            if (c[f[i]] == '=' && f[i] > f[t[a[i]]]) //更新等于号
                t[a[i]] = i;
        }
        printf("%d
    ", ans);
        Print(last);//输出方案
        return 0;
    }
    

    D. 跳跳棋

    题目描述

    • 跳跳棋是在一条数轴上进行的。棋子只能摆在整点上。每个点不能摆超过一个棋子。
    • 我们用跳跳棋来做一个简单的游戏:棋盘上有 3 颗棋子,分别在 a,b,c 这三个位置。我们要通过最少的跳动把它们的位置移动成 x,y,z 。(棋子是没有区别的)
    • 跳动的规则很简单,任意选一颗棋子,对一颗中轴棋子跳动。跳动后两颗棋子距离不变。一次只允许跳过 1 颗棋子。
    • 写一个程序,首先判断是否可以完成任务。如果可以,输出最少需要的跳动次数。

    输入格式

    • 第一行包含三个整数,表示当前棋子的位置 a,b,c。(互不相同)
    • 第二行包含三个整数,表示目标位置 x,y,z。(互不相同)

    输出格式

    • 如果无解,输出一行 NO。
    • 如果可以到达,第一行输出 YES ,第二行输出最少步数。

    样例输入

    1 2 3
    0 3 5
    

    样例输出

    YES
    2
    

    数据范围与提示

    • 20% 输入整数的绝对值均不超过 10。
    • 40% 输入整数的绝对值均不超过 10000。
    • 100% 绝对值不超过 (10^9)

    Solve

    • 可知有共有4种跳法:2跳到1前面,2跳到3后面,1跳到2后面,3跳到2前面;前两种跳法越跳越宽,一直跳下去必定爆炸,而后两种跳法得满足一定的条件,令(d_1=x_2-x_1,d_2=x_3-x_2),只有当(d_1 e d_2)时才可以跳,最后的情况一定是(d_1=d_2),所有的状态最后都会跳到唯一情况的(d_1=d_2),这就好像一颗树,树根是最后的状态,
      开始就进行判断,如果两个状态树根都不一样,那就不可到达,然后二分找跟过程可以,类比倍增球lca,但还是有差别

    Code

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int M = 0x3f3f3f3f;
    struct Node {
        int x, y, z;
        void Init() {
            scanf("%d%d%d", &x, &y, &z);
            if (x > y) swap(x, y);
            if (x > z) swap(x, z);
            if (y > z) swap(y, z);
        }
        bool operator != (const Node &b) const {
            return x != b.x || y != b.y || z != b.z;
        }
        bool operator == (const Node &b) const {
            return x == b.x && y == b.y && z == b.z;
        }
    }a, b;
    int s, s1, s2;
    Node Jump(Node a, int lim) {
        s = 0;
        while (lim) {
            int d1 = a.y - a.x, d2 = a.z - a.y, k;
            if (d1 == d2) return a;
            if (d1 < d2) {
                k = min((d2 - 1) / d1, lim);
                a.x += k * d1; a.y += k * d1;
            }
            else {
                k = min((d1 - 1) / d2, lim);
                a.y -= k * d2; a.z -= k * d2;
            }
            lim -= k;
            s += k;
        }
        return a;
    }
    int main() {
        a.Init(); b.Init();
        Node ra = Jump(a, M); s1 = s;
        Node rb = Jump(b, M); s2 = s;
        if (ra != rb) return puts("NO"), 0;
        if (s1 < s2) swap(s1, s2), swap(a, b);
        a = Jump(a, s1 - s2);
        int l = 0, r = s2;
        while (l < r) {
            int mid = l+r >> 1;
            if (Jump(a, mid) == Jump(b, mid)) r = mid;
            else l = mid + 1;
        }
        printf("YES
    %d
    ", l * 2 + s1 - s2);
        return 0;
    }
    
  • 相关阅读:
    Quartz使用总结(转)
    JAVA中使用LOG4J记录日志
    Java用HttpsURLConnection访问https网站的时候如何跳过SSL证书的验证?
    JAVA_HOME设置
    命令行启停mysql数据库
    本地jar包引入到maven项目中
    Could not clean server of obsolete files
    python基础一
    11-数据的增删改
    10-外键的变种 三种关系
  • 原文地址:https://www.cnblogs.com/shawk/p/13375428.html
Copyright © 2011-2022 走看看