Wannafly 挑战赛 9

比赛链接: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 }
View Code

 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 }
View Code

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 }
View Code

 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 }
View Code

 WA:没有想到由Catalan的证明扩展到一般情况。赛中没有细想,赛后逐步推导发现答案很简单。

E. 组一组

F. 导一导

原文地址:https://www.cnblogs.com/skyette/p/8408509.html