1. 程式人生 > >The 2018 ACM-ICPC Asia Qingdao Regional Contest(部分題解)

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\n", 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\n", 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\n");
            continue;
        }
        if(n == m) {
            printf("Richman\n");
            continue;
        }
        if(m == 0) {
            printf("%lld\n", 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\n", 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\n", d[i].s, d[i].e);
34         }*/
35 
36         if(ds > 2) {
37             printf("0\n");
38         } else if(ds == 2) {
39             printf("6\n");
40         } else if(ds == 1) {
41             printf("%d\n", 2 * (n - 1));
42         } else if(ds == 0) {
43             printf("%d\n", (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\n", r);
        else
            printf("%lld\n", 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\n",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\n",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 \n", 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\n", sum1, sum2);
51         printf("Case #%lld: %lld\n",t,(sum1 - sum2 + mod) % mod);
52     }
53     return 0;
54 }