• 20200716模拟赛2题解


    A. Layout

    题目描述

    • 和人类一样,奶牛们在打饭的时候喜欢和朋友站得很近。
    • 约翰的编号为(1)(n)(nleft ( 2leq nleq 1000 ight ))只奶牛正打算排队打饭。现在请你来安排她们,让她们在数轴上排好队。奶牛的弹性很好,同一个坐标可以站无限只奶牛,排队的顺序必须和她们编号的顺序一致。有(M)对奶牛互相爱慕,她们之间的距离不能超过一定的值,有(K)对奶牛互相敌视,她们的距离不能小于一定的值。
    • 那么,首尾奶牛的最大距离是多少呢?

    输入格式

    • 第一行输入(n,M,K,left ( 0< M,Kleq 5000 ight ))
    • 接下来(M)行每行三个整数(x,y,z),表示编号为(x)(y)的两头奶牛之间的距离最大不超过(z)
    • 再接下来(K)行每行三个整数(a,b,c),表示编号为(a)(b)的两头奶牛之间的距离最少为(c)

    输出格式

    • 如果没有合理方案,输出(-1),如果首尾两头牛的距离可以无限大,输出(-2),否则输出一个整数表示首尾奶牛的最大距离。

    样例输入

    4 2 1
    1 3 10
    2 4 20
    2 3 3
    

    样例输出

    27
    

    数据范围与提示

    • 四只牛分别在(0,7,10,27)

    Solve

    • 一道裸的差分约束,没建立超级源点,丢了10分。
    • 建立超级源点是考虑到图的一部分不联通,有可能是-1判断成-2

    Code

    #include <queue>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    const int N = 1e3+5, M = 12005;
    struct Side {
        int t, d, next;
    }e[M];
    int head[N], tot;
    void Add(int x, int y, int z) {
        e[++tot] = (Side) {y, z, head[x]};
        head[x] = tot;
    }
    int n, m1, m2, d[N], cnt[N];
    bool v[N];
    deque<int> q;
    int Spfa(int u) {
        memset(d, 0x3f, sizeof(d));
        memset(v, 0, sizeof(v));
        memset(cnt, 0, sizeof(cnt));
        while (!q.empty()) q.pop_front();
        d[u] = 0;
        q.push_back(u); v[u] = 1;
        cnt[u] = 1;
        while (!q.empty()) {
            int x = q.front();
            q.pop_front(); v[x] = 0;
            for (int i = head[x]; i; i = e[i].next) {
                int y = e[i].t;
                if (d[y] > d[x] + e[i].d) {
                    d[y] = d[x] + e[i].d;
                    if (v[y]) continue;
                    v[y] = 1;
                    if (++cnt[y] > n) return -1;
                    if (!q.empty() && d[y] <= d[q.front()])
                        q.push_front(y);
                    else q.push_back(y);
                }
            }
        }
        if (d[n] == 0x3f3f3f3f) return -2;
        return d[n];
    }
    int main() {
        scanf("%d%d%d", &n, &m1, &m2);
        for (int i = 1; i < n; ++i)
            Add(i + 1, i, 0);
        while (m1--) {
            int x, y, z;
            scanf("%d%d%d", &x, &y, &z);
            if (x > y) swap(x, y);
            Add(x, y, z);
        }
        while (m2--) {
            int x, y, z;
            scanf("%d%d%d", &x, &y, &z);
            if (x > y) swap(x, y);
            Add(y, x, -z);
        }
        for (int i = 1; i <= n; ++i)
            Add(0, i, 0);
        if (Spfa(0) == -1) puts("-1");
        else printf("%d
    ", Spfa(1));
        return 0;
    }
    

    B. 游戏

    题目描述

    • Mirko和Slavko爱玩弹球戏。在一个令人激动的星期五,Mirko和Slavko玩了一把弹球游戏。Mirko构建一个有向图,所有顶点最多有1条出边。弹球从1个顶点出发可以沿着一条边移动到它的邻接点,只要它存在,而且它会继续移动到后者的邻接点去,直到最后到达一个找不到出边的顶点才停下来。如果不存在这样的点,弹球可能无限运动下去。

    • 为了确信Slavko理解游戏的规则,Mirko将发起一系列询问,询问的类型如下:

      • 1 X:除非弹球陷入循环,弹球从X出发,最终将在哪个点停下来。
      • 2 X:删除X的出边(保证该边总是存在)
    • 注意:询问是按顺序执行的。

    输入格式

    • 第一行包含一个正整数N(left ( 1leq Nleq 3 imes 10^5 ight )),表示图的定点数。
    • 第二行包含由空格隔开N个正整数,第i个数表示从i顶点可以通过出边到达的定点编号。0表示该点没有出边。
    • 接下来的一行包含1个整数Q(left ( 1leq Qleq 3 imes 10^5 ight )),表示询问的次数。格式如上所示。

    输出格式

    • 对于第1类询问,输出弹球停止时所在顶点编号,每行1个,按照查询的顺序输出。如果弹球无法停止,则输出CIKLUS.

    样例输入

    3
    2 3 1
    7
    1 1
    1 2
    2 1
    1 2
    1 1
    2 2
    1 2
    

    样例输出

    CIKLUS
    CIKLUS
    1
    1
    2
    

    Solve

    • 用并查集解决,删边不好处理,可以倒过来进行加边。

    Code

    #include <cstdio>
    using namespace std;
    const int N = 3e5+5;
    int n, m, t[N], f[N], od[N], x[N], ans[N], v[N], cnt;
    bool c[N];
    int found(int x) {
        if (v[x] == cnt) return c[x] = 1, f[x] = x;
        v[x] = cnt;
        return x == f[x] ? x : (f[x] = found(f[x]));
    }
    int main() {
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &t[i]);
            f[i] = t[i] ? t[i] : i;
        }
        scanf("%d", &m);
        for (int i = 1; i <= m; ++i) {
            scanf("%d%d", &od[i], &x[i]);
            if (od[i] == 2) f[x[i]] = x[i];
        }
        for (int i = m; i; --i)
            if (od[i] == 1) {
                ++cnt;
                ans[i] = found(x[i]);
                if (c[ans[i]]) ans[i] = -1;
            }
            else f[x[i]] = t[x[i]];
        for (int i = 1; i <= m; ++i)
            if (ans[i] == -1) puts("CIKLUS");
            else if (ans[i]) printf("%d
    ", ans[i]);
        return 0;
    }
    

    C. 数字

    题目描述

    • 一个数字被称为好数字当他满足下列条件:
      1. 它有(2 imes n)个数位,n是正整数(允许有前导0)。
      2. 构成它的每个数字都在给定的数字集合S中。
      3. 它前n位之和与后n位之和相等或者它奇数位之和与偶数位之和相等。
    • 例如对于(n= 2,S= left { 1,2 ight }),合法的好数字有1111,1122,1212,1221,2112,2211,2222这样8种。
    • 已知n,求合法的好数字的个数(mod 999983)

    输入格式

    • 第一行一个数n。
    • 接下来一个长度不超过10的字符串,表示给定的数字集合(不存在重复的数字)。

    输出格式

    • 一行,一个整数表示合法的好数字的个数(mod 999983)

    样例输入

    2
    0987654321
    

    样例输出

    1240
    

    数据范围与提示

    • 对于20%的数据,(nleq 7)
    • 对于100%的数据,(nleq 1000,left | S ight |leq 10)

    Solve

    • 先DP处理出长度为i且所有数字和为j的方案数
      (dp[i][j]=sum_{j=0}^{i*Max\_a_i}sum_{k=1}^{len(s) } dp[i-1][j-a[k]])
    • 用分步乘法原理可得
      (ans=2 imes sum_{i=0}^{n*Max\_a_n} (dp[n][i])^2)
    • 这样会有重复,根据容斥原理可得
      (ans-=sum_{i=0}^{frac {n+1}{2}*Max\_a_n}(dp[frac{n+1}{2}][i])^2-sum_{j=0}^{frac{n}{2}*Max\_a_n}(dp[frac{n}{2}][j])^2)
    • 具体为什么是减这个数,可以看一下这一段

    Code

    #include <cstdio>
    #include <cstring>
    using namespace std;
    const int N = 1005, M = 999983;
    char c[15];
    int n, a[15];
    long long f[N][N*9], ans, s1, s2;
    long long sum(int n) {
        long long s = 0;
        for (int j = 0; j <= n * 9; ++j)
            s += f[n][j] * f[n][j] % M, s %= M;
        return s;
    }
    int main() {
        scanf("%d%s", &n, c + 1);
        a[0] = strlen(c+1);
        for (int i = 1; i <= a[0]; ++i)
            a[i] = c[i] - '0';
        f[0][0] = 1;
        for (int i = 1; i <= n; ++i)
            for (int j = 0; j <= i * 9; ++j)
                for (int k = 1; k <= a[0]; ++k)
                    if (j >= a[k]) 
                        f[i][j] += f[i-1][j-a[k]], f[i][j] %= M;
        ans = sum(n);
        ans = (ans + ans) % M;
        s1 = sum(n+1 >> 1);
        s2 = sum(n >> 1);
        ans -= s1 * s2 % M;
        ans = (ans + M) % M;
        printf("%lld", ans);
        return 0;
    }
    

    D. 水站

    题目描述

    • 已知有一个n层的水站:
      • (W_i)表示未操作之前第i层的已有水量;
      • (L_i)表示第i个水站能够维持或者储存的水的重量;
      • (P_i)表示在第i层进行减压放水操作所需的费用.
    • 被压减放水层所储存的所有水都将流向下一层。如果第i层的水量比(L_i)大,则这一层也会(自动)减压(不需要任何费用)。
    • 现在想要使最后一层减压(第n级),求最少的花费。这个任务现在交给了你。
      输入格式
    • 每个输入的第一行包含一个自然数n(left ( 1leq nleq 1.5 imes 10^5 ight ))
    • 接下来n行每行包含3个数(W_i,L_i,P_ileft ( 0leq W_i,L_i,P_ileq 1.5 imes 10^5 ight ))
      输出格式
    • 第一行输出所需的最小费用
    • 第二行若干个整数,从小到大输出必须减压的层的编号。

    样例输入

    3
    1000 1000 1
    0 1000 2
    2 10 100
    

    样例输出

    3
    1 2
    

    数据范围与提示

    • 给第一层和第二层减压
    • (30%:nleq 5000)
    • (100%:nleq 1.5 imes 10^5)

    Solve

    • 我们首先要知道最优解一定是从一个点开始到n,中途没有断掉,根据这可以写出(O(n^2))的暴力,原题就可以过了。
    • 当数据加强到1e5的时候就得考虑别的方法,可以考虑二分,对于每层来说,从上面找到一个最高的一层,使得总水量不会让这层溢出,这样这层就会对这些的价值产生影响,在用差分数组进行优化,时间复杂度为(O(n log n))

    Code

    #include <cstdio>
    #include <vector>
    using namespace std;
    const int N = 15e4+5;
    int n, w[N], m[N], p[N], s[N], c[N], ans = 1<<30, h;
    int main() {
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i)
            scanf("%d%d%d", &w[i], &m[i], &p[i]),
            s[i] = s[i-1] + w[i];
        for (int i = 1; i <= n; ++i) {
            int l = 1, r = i + 1;
            while (l < r) {
                int mid = l+r >> 1;
                if (s[i] - s[mid-1] <= m[i]) r = mid;
                else l = mid + 1;
            }
            c[r] += p[i];
            c[i+1] -= p[i];
        }
        for (int i = 1; i <= n; ++i) {
            c[i] += c[i-1];
            if (ans > c[i]) ans = c[i], h = i;
        }
        printf("%d
    ", ans);
        for (int i = h; i <= n; i++)
            if (s[i] - s[h-1] <= m[i])
                printf("%d ", i);
        return 0;
    }
    
  • 相关阅读:
    Vuzzer自动漏洞挖掘工具简单分析附使用介绍
    从程序员到项目经理(九):程序员加油站 -- 再牛也要合群
    从程序员到项目经理(八):程序员加油站 -- 不要死于直率
    从程序员到项目经理(七):程序员加油站 -- 完美主义也是一种错
    从程序员到项目经理(六):程序员加油站 -- 懂电脑更要懂人脑
    从程序员到项目经理(五):程序员加油站 -- 不是人人都懂的学习要点
    从程序员到项目经理(四):外行可以领导内行吗
    从程序员到项目经理(三):认识项目经理
    从程序员到项目经理(二)
    从程序员到项目经理(一)
  • 原文地址:https://www.cnblogs.com/shawk/p/13324635.html
Copyright © 2020-2023  润新知