The 2018 ACM-ICPC Asia Qingdao Regional Contest(部分题解)
摘要:
本文是The 2018 ACM-ICPC Asia Qingdao Regional Contest(青岛现场赛)的部分解题报告,给出了出题率较高的几道题的题解,希望熟悉区域赛的题型,进而对其他区域赛的准备有借鉴意义。
题意
给出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 }
题意
输入书的总本数和已经购买的书的本数以及每本书的价格
问按照他的购买策略这个人最多带了多少钱,购买策略就是从前往后只要手中的钱够买当前这本书就买,不够买就跳过。
解题思路
除去样例中给出的三个特例,分别是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; }
题意
给出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 }
题意
给出植物数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; }
之后的是另外一次练习的两道题
题意
给出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 }
题意
给出人数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 }