• The 2018 ACM-ICPC Asia Qingdao Regional Contest(部分题解)


    摘要:

      本文是The 2018 ACM-ICPC Asia Qingdao Regional Contest(青岛现场赛)的部分解题报告,给出了出题率较高的几道题的题解,希望熟悉区域赛的题型,进而对其他区域赛的准备有借鉴意义。


    Function and Function

    题意

    给出x和k,计算gk(x)。

    解题思路

    通过观察发现,g函数经过一定次数的递推一定会在0和1之间变换,所以循环内加判断提前结束递推即可。

    易错分析

    注意计算f(0)返回的是1的问题,下面的写法避免了这种错误。

    代码实现

     1 #include <cstdio>
     2 using namespace std;
     3 
     4 typedef long long ll;
     5 ll a[] = {1,0,0,0,1,0,1,0,2,1};
     6 
     7 ll f(ll x) {
     8     ll s = 0;
     9     while(x) {
    10         s += a[x%10];
    11         x /= 10;
    12     }
    13     return s;
    14 }
    15 ll g(ll x, ll k) {
    16     ll g0 = x, g1;
    17     for(ll i = 1; i <= k; i++) {
    18         if(g0 == 0) {
    19             if((k - i) & 1)
    20                 return 0;
    21             else
    22                 return 1;
    23         }
    24         g1 = f(g0);
    25         g0 = g1;
    26         //printf("#%lld
    ", g0);
    27     }
    28     return g0;
    29 }
    30 int main()
    31 {
    32     int T;
    33     scanf("%d", &T);
    34     ll x, k;
    35     while(T--) {
    36         scanf("%lld%lld", &x, &k);
    37         printf("%lld
    ", g(x, k));
    38     }
    39     return 0;
    40 }

    Books

    题意

    输入书的总本数和已经购买的书的本数以及每本书的价格

    问按照他的购买策略这个人最多带了多少钱,购买策略就是从前往后只要手中的钱够买当前这本书就买,不够买就跳过。

    解题思路

    除去样例中给出的三个特例,分别是n == m,m == 0,k > m(其中k表示0的个数),剩下的就是一般情况,直接贪心不是正确的结果,因为有0的存在,所以需要仔细考虑,我们肯定要将价格为0的书先买走,然后从前往后买,买够m本书之后(sum),在剩下的书中选择一本最便宜的书买不起(也就是minx-1),最后总的钱数sum + minx - 1就是答案。

    助解样例

    5 2

    0 0 2 3 0

    7 4

    0 0 2 3 4 2 0 0

    4 2

    0 0 1 1

    代码

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    const int maxn = 1e5 + 10;
    const int inf = 1e9 + 5;
    ll a[maxn];
    
    int main()
    {
        int T;
        ll n, m;
        scanf("%d", &T);
        while(T--) {
            scanf("%lld%lld", &n, &m);
            ll k = 0;
            ll mi = inf;
            int i;
            for(i = 1; i <= n; i++) {
                scanf("%lld", &a[i]);
                if(a[i] == 0)
                    k++;
                if(mi > a[i])
                    mi = a[i];
            }
    
            if(k > m) {
                printf("Impossible
    ");
                continue;
            }
            if(n == m) {
                printf("Richman
    ");
                continue;
            }
            if(m == 0) {
                printf("%lld
    ", mi - 1);
                continue;
            }
    
            ll sum = 0;
            for(i = 1; i <= n; i++) {
                if(k == m)//易错
                    break;
                if(a[i] != 0) {
                    sum += a[i];
                    k++;
                }
            }
            mi = inf;
            for(; i <= n; i++) {
                if(a[i] != 0) 
                    mi = min(a[i], mi);
            }
            printf("%lld
    ", sum + mi - 1);
        }
        return 0;
    }

    Flippy Sequence

    题意

    给出01串的长度和两个01串,一次操作(a1,a2, a3,a4)表示将第一个串对应位置的0或者1取反,具体操作区间是[a1,a2]和[a3,a4]。问将这两个串变为相同的操作有多少种不同的方法。

    两种方法不同的规则是4个数的序列只要有一个不同即为不同。

    解题思路

    首先先将两个串不同的区间分成一段一段,然后分局段数ds的不同我们可以分情况讨论:

    1、ds > 2,不论如何区间操作,都不能使得两个串相同,故 ans = 0;

    2、ds == 2,由题中样例3可知,分别操作 * 2 + 前一段带中间 + 后一段带中间  +  当成一段扣去中间 * 2,故ans = 6;

    3、ds == 1,先考虑全不相同,也就是是一段不同的情况,只能是两个不相交区间的操作,所以就是2 * (段长 - 1),再考虑有相同前缀和相同后缀的情况,有前缀可以从前缀中选一个位置带上这一段,再扣去这一段,所以需要加上前缀的长度即可,同理需要加上后缀的长度,ans = 2 * ( z - 1) + 2 * q + 2 * h = 2 * (n - 1);

    4、ds == 0,易知 ans = n * (n + 1) / 2,注意可能超int范围。

    代码实现

     1 #include <cstdio>
     2 
     3 const int maxn = 1e6 + 7;
     4 char s[maxn], t[maxn];
     5 struct Node {
     6     int s, e;
     7 }d[maxn];
     8 int ds;
     9 
    10 int main()
    11 {
    12     int T;
    13     int n;
    14     scanf("%d", &T);
    15     while(T--) {
    16         scanf("%d", &n);
    17         scanf("%s%s", s, t);
    18 
    19         ds = 0;//记录段数,每段包含起点和终点
    20         for(int i = 0; i < n;) {
    21             if(s[i] != t[i]) {
    22                 d[ds].s = i;
    23                 while(s[i] != t[i]) {
    24                     i++;
    25                 }
    26                 d[ds++].e = i - 1;
    27             }
    28             else
    29                 i++;
    30         }
    31 
    32         /*for(int i = 0; i < ds; i++) {
    33             printf("%d %d
    ", d[i].s, d[i].e);
    34         }*/
    35 
    36         if(ds > 2) {
    37             printf("0
    ");
    38         } else if(ds == 2) {
    39             printf("6
    ");
    40         } else if(ds == 1) {
    41             printf("%d
    ", 2 * (n - 1));
    42         } else if(ds == 0) {
    43             printf("%d
    ", (long long)(n * (n + 1)) / 2);
    44         }
    45     }
    46     return 0;
    47 }

    Plants vs. Zombies

    题意

    给出植物数n和机器人能走的步数m,给出每棵植物的生长速度,然后问这个花园的防御值最大是多少

    这个花园的防御值是机器人走m步之后所有植物中防御值最小的那一个数值。

    解题思路

    最小值最大化问题,采用二分加验证的方法。

    代码实现

    #include<stdio.h>
    const int N = 1e5 + 5;
    
    long long n, m, a[N];
    
    bool A(long long x)
    {
        long long b[N] = {0};
        long long i, k, z;
        for(i = 0, k = m; i < n - 1; i++)
        {
            if(b[i] >= x)
            {
                if(k > 0)
                    k--;
                else
                    return 0;
                continue;
            }
            z = x - b[i];
            if(z % a[i])
                z = z / a[i] + 1;
            else
                z = z / a[i];
    
            if(k < z * 2 - 1)
                return false;
            k -= z * 2 - 1;
            b[i+1] += (z-1) * a[i+1];
        }
        if(b[i] < x)
        {
            z = x - b[i];
            if(z % a[i])
                z = z / a[i] + 1;
            else
                z = z / a[i];
            if(k < z * 2 - 1)
                return false;
        }
        return true;
    }
    
    int main()
    {
        long long t, i, x, r, l;
        scanf("%lld", &t);
        while(t--)
        {
            scanf("%lld%lld", &n, &m);
            r = 1e17;
            for(i = 0; i < n; i++)
            {
                scanf("%lld", &a[i]);
                if(r > a[i] * m)
                    r =  a[i] * m;
            }
            l = 0;
            while(r - l > 1)
            {
                x = (r + l) / 2;
                if(A(x))
                    l = x;
                else
                    r = x;
            }
            if(A(r))
                printf("%lld
    ", r);
            else
                printf("%lld
    ", l);
        }
        return 0;
    }

    之后的是另外一次练习的两道题

    World Cup

    题意

    给出5种比赛的票价,给出这个人看了几场比赛对应的比赛ID,问这个人看比赛花了多少钱

    解题思路

    水题,注意细节

    代码实现

     1 #include <stdio.h>
     2 int f[100];
     3 
     4 int main()
     5 {
     6     int T, t = 1;
     7     scanf("%d", &T);
     8     while(T--) {
     9         int a, b, c, d, e;
    10         scanf("%d%d%d%d%d", &a, &b, &c, &d, &e);
    11         int i;
    12         for(i = 1; i <= 48; i++)
    13             f[i] = a;
    14         for(i = 49 ; i <= 56; i++)
    15             f[i] = b;
    16         for(i = 57 ; i <= 60; i++)
    17             f[i] = c;
    18         for(i = 61 ; i <= 62; i++)
    19             f[i] = d;
    20         f[63] = e;
    21 
    22         int n;
    23         scanf("%d", &n);
    24         long long ans = 0;
    25         int x;
    26         for(i = 1; i <= n; i++) {
    27             scanf("%d", &x);
    28             ans += f[x];
    29         }
    30         printf("Case #%d: %lld
    ",t++, ans * 10000);
    31     }
    32     return 0;
    33 }

    Chat Group

    题意

    给出人数n和一个小组最少有多少人,问这些人能够组成多少个不同的小组

    解题思路

    很容易想到答案是C(n,k) + C(n,k+1) +...+ C(n,n)。由于数据量比较大,我们试着使用一种优化的算法,我们知道C(n,1) + C(n,k+1) +...+ C(n,n) = 2n ,由此可得ans = 2n - (C(n,0) + ..C(n,k -1)),前面一项我们使用整数快速幂(取模)计算,后一项使用递推公式计算它们的和(取模)。

    递推形式如下:

    C(n, k) = C(n, k - 1) * (n - k + 1) / k,所以迭代取模计算即可。

    迭代过程中使用乘法逆元处理除法计算。提前打好每个数的逆元表。

    代码如下

     1 #include<stdio.h> 
     2 const int mod=1000000007;
     3 long long inva[100100];
     4 
     5 long long pow(long long a,long long b)
     6 {
     7     long long s=1;
     8     while(b)
     9     {
    10         if(b&1)
    11             s = s * a % mod;
    12         a = a * a % mod;
    13         b /= 2;
    14     }
    15     return s;
    16 }
    17 long long inv(long long num)
    18 {
    19     return pow(num, mod-2);
    20 }
    21 long long sum(long long n,long long k)
    22 {
    23     long long i,s = 1, cn0 = 1, cn1;
    24     for(i = 1; i <= k - 1; i++) 
    25     {
    26         //printf("%lld %lld
    ",i, inv(i));
    27         cn1 = (cn0%mod * (n - i + 1)%mod)%mod;
    28         cn1 = (cn1%mod * inva[i] %mod)%mod;
    29         s = (s%mod + cn1%mod)%mod;
    30         cn0 = cn1;
    31     }
    32     return s;
    33 }
    34 
    35 int main()
    36 {
    37     long long T,n,k,sum1,sum2,t;
    38     for(long long i = 1; i <= 100010; i++) {
    39         inva[i] = inv(i);
    40         //printf("%lld 
    ", inva[i]);
    41     }
    42     //puts("");
    43     
    44     scanf("%lld",&T);
    45     for(t=1;t<=T;t++)
    46     {
    47         scanf("%lld%lld",&n,&k);
    48         sum1=pow(2,n);
    49         sum2=sum(n,k);
    50         //printf("%lld %lld
    ", sum1, sum2);
    51         printf("Case #%lld: %lld
    ",t,(sum1 - sum2 + mod) % mod);
    52     }
    53     return 0;
    54 }
  • 相关阅读:
    JS Array转JSON
    js数组转字符串并用,分割
    java枚举类-根据key获取value及根据value获取key
    CSS文件引入顺序
    git pull之前要先commit
    FastJson中@JSONField注解使用
    @JsonFormat与@DateTimeFormat注解的使用
    Jackson 时间格式化,时间注解 @JsonFormat 用法、时差问题说明
    shell脚本使用
    ubuntu12.04 安装redis
  • 原文地址:https://www.cnblogs.com/wenzhixin/p/9941685.html
Copyright © 2020-2023  润新知