比赛链接:https://www.nowcoder.com/acm/contest/71#question
AC:A(-4),C(-3)
A. 找一找
题意:给你一个长度为n(n <= 1e5) 的正整数序列a[] (a[i] <= 1e5)。问你有多少个i满足, 存在某个j和大于1的正整数k(k>1), 使得a[j] = a[i]*k。
观察:a[i]的范围比较小,可以对[1, 1e5]之内每一种数进行搜索,令上界为N,这里N=1e5,复杂度为 O(N/1+N/2+...+N/N) = O(N * log(N))。
方法:用一个int vis[1e5+1] 记录下每个数出现的次数就行了
code:
1 /* 2 by skydog 3 */ 4 #include <iostream> 5 #include <cstdio> 6 #include <vector> 7 #include <utility> 8 #include <algorithm> 9 #include <cmath> 10 #include <cstring> 11 #include <map> 12 #include <set> 13 #include <stack> 14 #include <queue> 15 #include <deque> 16 #include <cassert> 17 18 using namespace std; 19 typedef long long ll; 20 typedef pair<int, int> ii; 21 typedef pair<ll, ll> l4; 22 23 #define mp make_pair 24 #define pb push_back 25 #include<cstdio> 26 using namespace std; 27 28 const int maxn = 1e6+1; 29 int vis[maxn] = {0}; 30 inline int get(int a) 31 { 32 for (int j = 2*a; j < maxn; j += a) 33 if (vis[j]) 34 return true; 35 return false; 36 } 37 int main() 38 { 39 memset(vis, 0, sizeof(vis)); 40 int n; 41 scanf("%d", &n); 42 for (int i = 0; i < n; ++i) 43 { 44 int x; 45 scanf("%d", &x); 46 vis[x] += 1; 47 } 48 int ans = 0; 49 for (int i = 1; i < maxn; ++i) 50 if (vis[i]) 51 ans += vis[i]*get(i); 52 printf("%d ", ans); 53 }
WA:没有读清楚题目。
B. 数一数
题意:给你n (n <= 1e6) 个总长度不超过 2e6的string s[1...n],定义f(P, T) 为P在T中的出现次数。让你对于每一个i 属于[1, n], 求出 ans[i] = product of {f(s[i], s[j]), j = 1...n} mod 998244353。
观察:既然是求乘积,可以想到如果相乘的项中有一个为0,那么结果就是0了。那么很明显的是,如果一个P的长度大于T,或者P和T长度相等但是内容不同,那么f(P, T) 为0,所以乘积为0。所以只需要考虑长度最小一个的串mini,其他的串中,如果长度比mini.length()大,或者是长度等于mini.length()但是和mini不相同,那么答案是0。
方法:找一个长度最小的串mini,用kmp求出mini的答案result。输出结果s[i]的结果时,如果s[i]和mini相同,那么输出result,否则输出0。
code:
1 /* 2 by skydog 3 */ 4 #include <iostream> 5 #include <cstdio> 6 #include <vector> 7 #include <utility> 8 #include <algorithm> 9 #include <cmath> 10 #include <cstring> 11 #include <map> 12 #include <set> 13 #include <stack> 14 #include <queue> 15 #include <deque> 16 #include <cassert> 17 18 using namespace std; 19 typedef long long ll; 20 typedef pair<int, int> ii; 21 typedef pair<ll, ll> l4; 22 23 #define mp make_pair 24 #define pb push_back 25 26 const int maxn = 2e6+1; 27 const int mod = 998244353; 28 string str[maxn], mini; 29 int n; 30 int f[maxn]; 31 int kmp(const string &T) 32 { 33 int ret = 0; 34 for (int i = 0, j = 0; i < T.length(); ++i) 35 { 36 while (j != -1 && T[i] != mini[j]) 37 j = f[j]; 38 ++j; 39 if (j == mini.size()) 40 { 41 ++ret; 42 j = f[j]; 43 } 44 } 45 return ret; 46 } 47 int main() 48 { 49 ios::sync_with_stdio(false); 50 cin.tie(0); 51 cin >> n; 52 for (int i = 0; i < n; ++i) 53 cin >> str[i]; 54 mini = str[0]; 55 for (int i = 0; i < n; ++i) 56 if (str[i].length() < mini.length()) mini = str[i]; 57 for (int j = f[0] = -1, i = 0; i < mini.length(); ++i, ++j) 58 { 59 while (j != -1 && mini[i] != mini[j]) 60 j = f[j]; 61 f[i+1] = j+1; 62 } 63 int ans = 1; 64 for (int i = 0; i < n; ++i) 65 { 66 ans = (ll) ans * kmp(str[i]) % mod; 67 if (!ans) 68 break; 69 } 70 71 for (int i = 0; i < n; ++i) 72 if (str[i] == mini) 73 cout << ans << ' '; 74 else 75 cout << 0 << ' '; 76 77 }
WA: 没有想到f(P, T) 为0的条件。
C. 列一列
题意:考虑fibonacci数列,从第一项开始,A[1] = 1, A[2] = 2, A[k] = A[k-1]+A[k-2] when k >= 3。下面给你不超过三十个A[k],k保证不超过1e5(k<=1e5)。让你输出对应的k。
观察:本来是想fibonacci数列是有循环节的,找一个循环节很大的质数模一模来判重,但有几个问题:(1)对一个大整数取mod,在不使用大整数库的情况下,手写比较麻烦;(2)虽然循环节很大,但是一个循环节内,是可能存在mod后余数相等的情况的,即出现冲突。(1)第一个问题,我就考虑直接对10^k取模,方便很多,把大整数按照一个string读入,然后取它后k位就好了。(2)第二个问题,我在本机试了一下,发现mod 10^10时,刚好没有冲突。于是就去了mod 10^10
方法:暴力枚举计算数列前1e5项mod 10^10的值,放到一个map里。令数列项数上界为N,此处N=1e5,单次询问的时间复杂度时O(length of string + log(N))。
code:
1 /* 2 by skydog 3 */ 4 #include <iostream> 5 #include <cstdio> 6 #include <vector> 7 #include <utility> 8 #include <algorithm> 9 #include <cmath> 10 #include <cstring> 11 #include <map> 12 #include <set> 13 #include <stack> 14 #include <queue> 15 #include <deque> 16 #include <cassert> 17 18 using namespace std; 19 typedef long long ll; 20 typedef pair<int, int> ii; 21 typedef pair<ll, ll> l4; 22 23 #define mp make_pair 24 #define pb push_back 25 26 27 ll get(string s, int k) 28 { 29 int len = s.length(); 30 ll ret = 0; 31 for (int i = max(0, len-k); i < len; ++i) 32 { 33 ret = 10*ret + s[i]-'0'; 34 } 35 return ret; 36 } 37 ll ten(int k) 38 { 39 return k?10*ten(k-1):1; 40 } 41 map<ll, int> ma; 42 const int SZ = 1e5; 43 bool try_mod(ll mod) 44 { 45 vector<ll> f({1%mod, 2%mod}); 46 ma.clear(); 47 while (f.size() < SZ) 48 { 49 int sz = f.size(); 50 f.pb((f[sz-1]+f[sz-2])%mod); 51 } 52 for (int i = 0; i < f.size(); ++i) 53 ma[f[i]] = i+1; 54 55 cerr << "f.size() = " << f.size() << endl; 56 cerr << "ma.size() = " << ma.size() << endl; 57 return f.size() == ma.size(); 58 } 59 60 int main() 61 { 62 int k = 10; 63 assert(try_mod(ten(k))); 64 string s; 65 while (cin >> s) 66 { 67 ll n = get(s, k); 68 assert(ma.count(n)); 69 cout << ma[n] << endl; 70 } 71 return 0; 72 }
WA:应该用map,但是傻了用了vector加lower_bound。
D. 造一造
题意:将1到n (n<=1e6) 依次插入一个stack里,某一时刻若stack非空,你可以弹出stack中的元素,最后要使stack变空。给你m,k ( m, k <= 1e6),问要求第m个元素入stack之后,stack中恰好有k个元素的出栈入栈顺序有几种。答案mod 1e9+7。
观察:入栈顺序已经固定了嘛,所以,如果把1当作某个元素入栈,0当作某个元素出栈,我们需要确定了一个长度为2*n的01串,并且使任意时刻1出现的次数不小于0出现的次数,听起来很Catalan。所以就是要求有几个长度为2*n的Catalan序列,使得第m个1出现时(包含第m个1),1的个数 减 0的个数 等于k。
然后就想解决一个子问题solve(one, zero)即满足任意前缀中1的个数要大于等于0的个数,且有one个1,zero个0的方法数。答案就是solve(m-1, m-k) * solve(n+k-m, n-m) 。为什么是这个呢,因为我们要求放第m个1的时候,前面的元素合法,即前面有m-1个1和m-k个0,方法数是solve(m-1, m-k)。考虑放完m之后的位置,要继续放n-m个1和n-(m-k) = n+k-m个0,那么对于后缀,从后往前看,后缀合法的条件是任意一个后缀中0的个数不小于1的个数,即solve(n+k-m, n-m)。
那么solve(one, zero) 要怎么求呢?我们可以考虑Catalan数的证明。Catalan数等于将n个0和n个1排列,使得对于每个前缀,1的个数都不少于0的个数的方法数,计作C(n),C(n) = c(2n, n) - c(2n, n+1) = c(2n, n)/(n+1),其中小写字母c代表组合数。
在这里贴一个数形结合的证明 http://blog.miskcoo.com/2015/07/catalan-number。还用另一种本质相同的证明,简单说一下。将n个0和n个1排成一排的方法数为c(2n, n),即从2n个位置中选出n个放1。下面考虑从这c(2n, n)中除去不符合我们Catalan性质的序列。那么对于每一个不合法的序列,一定存在左边第一个位置,0的个数zero = (1的个数one)+1。然后我们将这前(zero+one)个数中1和0交换,得到了一个由(n+1)个1和(n-1)个0组成的排列。同时可以发现,对于任何一个由(n+1)个1和(n-1)个0组成的排列,我们一定能找到左边第一个位置,使得 1的个数one = (0的个数zero) + 1。那么将这前(one+zero)个数中的1和0交换,我们得到了一个由n个1和n个0组成的不合法排列。所以不合法序列 和 由(n+1)个1和(n-1)个0组成的排列 是一一对应的,数量也相同,不合法序列数 = 由(n+1)个1和(n-1)个0组成的排列的个数 = c(2n, n+1)。所以C(n) = c(2n, n) - c(2n, n+1)。
同理,我们可以推出solve(one, zero) = c(one+zero, one) - c(one+zero, one+1),即答案。
方法:预处理阶乘f[n]和阶乘的逆inv[n],快速求出组合数,然后按照上述公式输出答案。
坑:组合数的底数可以达到2*n,即2e6。
code:
1 /* 2 by skydog 3 */ 4 #include <iostream> 5 #include <cstdio> 6 #include <vector> 7 #include <utility> 8 #include <algorithm> 9 #include <cmath> 10 #include <cstring> 11 #include <map> 12 #include <set> 13 #include <stack> 14 #include <queue> 15 #include <deque> 16 #include <cassert> 17 18 using namespace std; 19 typedef long long ll; 20 typedef pair<int, int> ii; 21 typedef pair<ll, ll> l4; 22 23 #define mp make_pair 24 #define pb push_back 25 26 const int mod = 1e9+7; 27 const int maxn = 2e6+1; 28 ll f[maxn], inv[maxn]; 29 inline ll power(ll base, ll p) 30 { 31 ll ret = 1; 32 while (p) 33 { 34 if (p&1) ret = ret * base % mod; 35 base = base * base % mod; 36 p >>= 1; 37 } 38 return ret; 39 } 40 inline void init() 41 { 42 f[0] = 1; 43 for (int i = 1; i < maxn; ++i) f[i] = f[i-1] * i % mod; 44 inv[maxn-1] = power(f[maxn-1], mod-2); 45 for (int i = maxn-1; i >= 1; --i) inv[i-1] = inv[i] * i % mod; 46 } 47 ll cb(ll a, ll b) 48 { 49 if (a < b || b < 0) return 0; 50 return f[a] * inv[b] % mod * inv[a-b] % mod; 51 } 52 ll solve(ll one, ll zero) 53 { 54 ll all = one+zero; 55 ll ret = (cb(all, one)-cb(all, one+1))%mod; 56 if (ret < 0) ret += mod; 57 return ret; 58 } 59 int main() 60 { 61 init(); 62 int T; 63 scanf("%d", &T); 64 for (int kase = 1; kase <= T; ++kase) 65 { 66 int n, m, k; 67 scanf("%d %d %d", &n, &m, &k); 68 //prefix (m-1) one and (m-k) zero 69 //thus suffix n+k-m zero and n-m one 70 printf("%lld ", solve(m-1, m-k)*solve(n+k-m, n-m)%mod); 71 } 72 }
WA:没有想到由Catalan的证明扩展到一般情况。赛中没有细想,赛后逐步推导发现答案很简单。
E. 组一组
F. 导一导