codeforces Educational Round 80
A、Deadline
题意:
完成一个计划需要$d$天,但是可以优化,优化$x$天的情况下,完成时间是$x+\lceil \frac{d}{x+1} \rceil$天。求$n$天内能否完成?
题解:
枚举天数即可,显然只需要枚举到$sqrt(d)$,如果可行马上输出。直接计算也可以,但是博主不会。
AC代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int N = 1e5 + 5; 4 void solve() 5 { 6 int n, d; 7 scanf("%d%d", &n, &d); 8 for (int i = 0; i * i <= d; ++i) 9 { 10 int ans = 0; 11 if (d % (i + 1)) 12 ans = d / (i + 1) + 1 + i; 13 else 14 ans = d / (i + 1) + i; 15 if (n >= ans) 16 { 17 puts("YES"); 18 return; 19 } 20 } 21 puts("NO"); 22 } 23 int main() 24 { 25 int T; 26 scanf("%d", &T); 27 while (T--) 28 solve(); 29 return 0; 30 }
B、Yet Another Meme Problem
题意:
数字$a$范围在$[1,A]$,$b$范围在$[1,B]$,将$a$和$b$拼接起来,求这个数字等于$a*b+a+b$的值时,$a$和$b$取值的对数。
题解:
我们令$b$的位数是$k$,则$con(a,b) = a*10^k+b$,然后化简得$b=10^k-1$时即可。所以就找符合的$b$即可,此时$a$任意。
AC代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N = 1e5 + 5; 5 void solve() 6 { 7 ll n, m; 8 scanf("%lld%lld", &n, &m); 9 ll cnt = 0; 10 for (ll i = 10; i - 1 <= m; i *= 10) 11 ++cnt; 12 printf("%lld\n", cnt * n); 13 } 14 int main() 15 { 16 int T; 17 scanf("%d", &T); 18 while (T--) 19 solve(); 20 return 0; 21 }
*C、Two Arrays
题意:
给出$n$和$m$,求出长度都是$m$的序列$a$和$b$,使得对于所有的$1 \leq i \leq m$:$a_i \leq n, b_i \leq n, a_i \leq a_{i+1},b_i \geq b_{i+1},a_i \leq b_i$。
题解:
解法一、
令$dp[i][j]$表示第$i$个数的$b_i-a_i=j$的方案数,转移方程:
$dp[i][j] = \sum \limits _{k = j}^{n-1} dp[i-1][k]*(k-j+1)$
可以使用前缀和优化,但是不优化也能过。
AC代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N = 1e3 + 5, M = 1e1 + 5; 5 const ll mod = 1e9 + 7; 6 ll dp[M][N], sum[N]; 7 void solve() 8 { 9 int n, m; 10 scanf("%d%d", &n, &m); 11 for (int i = 0; i < n; ++i) 12 dp[1][i] = n - i; 13 for (int i = 2; i <= m; ++i) 14 for (int j = 0; j < n; ++j) 15 for (int k = j; k < n; ++k) 16 dp[i][j] = (dp[i][j] + dp[i - 1][k] * (k - j + 1)) % mod; 17 ll ans = 0; 18 for (int i = 0; i < n; ++i) 19 ans = (ans + dp[m][i]) % mod; 20 printf("%lld\n", ans); 21 } 22 int main() 23 { 24 int T = 1; 25 //scanf("%d", &T); 26 while (T--) 27 solve(); 28 return 0; 29 }
解法二、
官方题解的做法,反正我是惊呆了。
考虑长度是$2*m$的序列$a_1,a_2,...,a_m,b_m,...b_2,b_1$,这个序列是单调不降的,等价于从$1$到$n$中选择$2*m$个数组成的可重集的数量的和。所以答案就是$C_{n+2*m-1}^{2*m}$
取模求个逆元就行。
AC代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N = 1.1e3 + 5; 5 const ll mod = 1e9 + 7; 6 ll pow(ll a, ll b, ll p) 7 { 8 ll res = 1; 9 while (b) 10 { 11 if (b & 1) 12 res = res * a % p; 13 a = a * a % p; 14 b >>= 1; 15 } 16 return res; 17 } 18 ll fac[N]; 19 void solve() 20 { 21 fac[0] = 1; 22 for (int i = 1; i < N; ++i) 23 fac[i] = fac[i - 1] * i % mod; 24 int n, m; 25 scanf("%d%d", &n, &m); 26 printf("%lld\n", fac[n + 2 * m - 1] * pow(fac[n - 1], mod - 2, mod) % mod * pow(fac[2 * m], mod - 2, mod) % mod); 27 } 28 int main() 29 { 30 int T = 1; 31 //scanf("%d", &T); 32 while (T--) 33 solve(); 34 return 0; 35 }
*D、Minimax Problem
题意:
给出$n$行$m$列的矩阵,$n \leq 3e5,m \leq 8$。求两行(可以一样),使得$min(max _{1\leq i \leq m} (a_i, b_i))$最大。
题解:
使最小值最大,必然先考虑二分答案。但是我们要怎么验证?注意到二分的答案的含义是:存在一个选法使得结果大于这个答案,那么取右半,否则取左半。如果$a_i$和$b_i$只要有一个大于这个答案,$max$就大于这个答案,所以考虑把大于等于设成$1$,否则设成$0$,然后状压。将每一列变成一个$[0.255]$的数,然后相当于是找两个数,使得$c_i | c_j = 2^m-1$,暴力找时间复杂度不对,考虑剪枝:如果一个数本身就是$2^m-1$,直接返回,如果一个数是$0$跳过。还可以建图寻找,但是实际上使用剪枝即可通过。注意!如果最终找不到大于$0$的答案,输出$1\ 1$。
AC代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int N = 3e5 + 5; 4 int n, m, px, py; 5 int a[N][15]; 6 int buc[305]; 7 bool check(int x) 8 { 9 memset(buc, 0, sizeof(buc)); 10 for (int i = 1; i <= n; ++i) 11 { 12 int tmp = 0; 13 for (int j = 1; j <= m; ++j) 14 tmp |= (a[i][j] >= x) << (j - 1); 15 buc[tmp] = i; 16 } 17 for (int i = 0; i < (1 << m); ++i) 18 for (int j = 0; j < (1 << m); ++j) 19 if (buc[i] && buc[j] && (i | j) == (1 << m) - 1) 20 { 21 px = buc[i], py = buc[j]; 22 return true; 23 } 24 return false; 25 } 26 void solve() 27 { 28 scanf("%d%d", &n, &m); 29 for (int i = 1; i <= n; ++i) 30 for (int j = 1; j <= m; ++j) 31 scanf("%d", &a[i][j]); 32 int l = 0, r = 1e9 + 5; 33 while (l < r) 34 { 35 int m = (l + r) >> 1; 36 if (!check(m)) 37 r = m; 38 else 39 l = m + 1; 40 } 41 if (!l) 42 printf("1 1\n"); 43 else 44 printf("%d %d\n", px, py); 45 } 46 int main() 47 { 48 int T = 1; 49 //scanf("%d", &T); 50 while (T--) 51 solve(); 52 return 0; 53 }
E、Messenger Simulator
题意:
给出一个$n$个人的序列,编号$1~n$,和$m$次操作,每次操作可以让一个人到最前面,然后他前面的人往后退一个人,求这个过程中每个人最前和最后的位置。
题解:
这不是$splay$裸题吗。
这个题目实际上就是查找树节点在第$k$大和插入到最左边。所以$splay$可做,但是太麻烦,没必要。
求第$k$大可以用树状数组或者权值线段树,然后考虑到这些数都是往最前面移,所以后面移动的一定在前面移动的前面,所以就建立一棵大小是$m+n$的树状数组,后$n$个数置为$1$,前$m$个数置为$0$,记录初始序列人的位置是$pos[a_i] = m+i$,然后第$i$次操作,就先统计一下当前需要操作的结点的第$k$大,然后移到$m-i+1$,更新$pos$数组,在所有操作完成后再更新一次所有位置即可。
AC代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int N = 3e5 + 5; 4 struct BIT 5 { 6 int c[N << 1]; 7 void update(int pos, int n, int v) 8 { 9 for (; pos <= n; pos += pos & (-pos)) 10 c[pos] += v; 11 } 12 int query(int pos) 13 { 14 int res = 0; 15 for (; pos; pos -= pos & (-pos)) 16 res += c[pos]; 17 return res; 18 } 19 }; 20 BIT tr; 21 pair<int, int> ans[N]; 22 int pos[N]; 23 void solve() 24 { 25 memset(tr.c, 0, sizeof(tr.c)); 26 int n, m, a; 27 scanf("%d%d", &n, &m); 28 for (int i = 1; i <= n; ++i) 29 ans[i] = {i, i}, pos[i] = m + i; 30 for (int i = m + 1; i <= m + n; ++i) 31 tr.update(i, m + n, 1); 32 for (int i = 1; i <= m; ++i) 33 { 34 scanf("%d", &a); 35 int p = pos[a]; 36 ans[a].second = max(ans[a].second, tr.query(p)); 37 ans[a].first = 1; 38 tr.update(p, m + n, -1); 39 pos[a] = m + 1 - i; 40 tr.update(pos[a], m + n, 1); 41 } 42 for (int i = 1; i <= n; ++i) 43 ans[i].second = max(ans[i].second, tr.query(pos[i])); 44 for (int i = 1; i <= n; ++i) 45 printf("%d %d\n", ans[i].first, ans[i].second); 46 } 47 int main() 48 { 49 int T = 1; 50 //scanf("%d", &T); 51 while (T--) 52 solve(); 53 return 0; 54 }