牛客OI周赛15-普及组
参考文献:
https://ac.nowcoder.com/discuss/400201?type=101&order=0&pos=9&page=1
https://www.acwing.com/video/125/
A题 咪咪游戏:https://ac.nowcoder.com/acm/contest/4911/A
签到题。
算出长度,显然奇数不行,然后遍历一遍,奇数位为'm',偶数位为'q'。(下标从1开始)
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int N = 1e5 + 10; 4 5 char s[N]; 6 7 int main() 8 { 9 int t; cin >> t; 10 while(t --) 11 { 12 scanf("%s", s + 1); 13 int len_s = strlen(s + 1); 14 int mark = 1; 15 if(len_s & 1) mark = 0; 16 for(int i = 1; i <= len_s; i ++) 17 { 18 if(i & 1) 19 { 20 if(s[i] == 'm') continue; 21 else { mark = 0; break; } 22 } 23 else 24 { 25 if(s[i] == 'q') continue; 26 else { mark = 0; break; } 27 } 28 } 29 if(mark) puts("Yes"); 30 else puts("No"); 31 } 32 return 0; 33 }
B题 三角形:https://ac.nowcoder.com/acm/contest/4911/B
原题:https://www.acwing.com/problem/content/148/
思路:
M个序列里分别选一个数,我们先从第一行和第二行来合并出新行,再用新行与第三合并,依次类推,最后的新行的前K个数的即为答案。
设数组A为第一行,数组B为第二行。合并的为C数组
先将A从小到大排序,那么我可以得到的新数组为:
A【1】+ B【1】, A【1】+ B【2】,A【1】+ B【3】...... A【1】+ B【M【B】】(M【B】为B的个数)
A【2】+ B【1】, A【2】+ B【2】,A【2】+ B【3】...... A【2】+ B【M【B】】
A【3】+ B【1】, A【3】+ B【2】,A【3】+ B【3】...... A【3】+ B【M【B】】
A【4】+ B【1】, A【4】+ B【2】,A【4】+ B【3】...... A【4】+ B【M【B】】
......
A【M【A】】+ B【1】,...... 共M【A】* M【B】个
显然每列的第一个数为每列的最小值
我们的目的是求前K个最小值,所以我们C数组的长度为 min( K, M【A】* M【A】);
对应合并我们需要的操作是求最小值,插入新的值和删除,显然优先队列可以满足我们的条件,先将第一行放入优先队列,并加一个下标标记,对于取出的最小值,就插入他对应的下一行。直到取到K个值就可以了。总共合并N - 1 次即可。
1 #include <cstdio> 2 #include <queue> 3 #include <algorithm> 4 #include <vector> 5 #include <iostream> 6 using namespace std; 7 const int N = 110, K = 1e4 + 10; 8 typedef pair<int, int> PII; 9 10 int n, k, f[N][N]; // f 为 第 i 行 的 宝物值 11 int a[K], b[K], c[K], cnt[N]; // cnt 为 每一行的宝物个数 12 int len; // len是 a 数组的长度 13 14 void merge(int ma, int mb) // ma 是 a 数组的长度; mb 是数组 b 的长度 15 { 16 priority_queue<PII, vector<PII>, greater<PII> > heap; // 小根堆 17 for(int i = 1; i <= mb; i ++) heap.push({a[1] + b[i], 1}); // 插入第一行 18 for(int i = 1; i <= min(k, ma * mb); i ++) 19 { 20 auto t = heap.top(); 21 heap.pop(); 22 int s = t.first, p = t.second; 23 c[i] = s; 24 // s - a[p] + a[p + 1] = a[p] + b[?] - a[p] + a[p + 1] = a[p + 1] + b[?]; 25 if(p + 1 <= ma) heap.push({s - a[p] + a[p + 1], p + 1}); 26 } 27 28 for(int i = 1; i <= min(k, ma * mb); i ++) a[i] = c[i]; // 再放入 a 数组 29 len = min(k, ma * mb); // 更新 a 数组长度 30 } 31 32 int main() 33 { 34 cin >> n >> k; 35 for(int i = 1; i <= n; i++) 36 { 37 int m; scanf("%d", &m); 38 for(int j = 1; j <= m; j ++) scanf("%d", &f[i][j]); 39 cnt[i] = m; 40 } 41 42 for(int i = 1; i <= cnt[1]; i ++) a[i] = f[1][i]; // 得到 a 数组 43 sort(a + 1, a + cnt[1] + 1); // 对 a 排序 44 len = cnt[1]; // 记录 a 数组的长度 45 for(int i = 2; i <= n; i ++) // n - 1 次合并 46 { 47 for(int j = 1; j <= cnt[i]; j ++) b[j] = f[i][j]; // 得到 b 数组 48 merge(len, cnt[i]); // 合并 49 } 50 51 long long ans = 0; 52 for(int i = 1; i <= k; i ++) ans += a[i]; 53 cout << ans << endl; 54 55 return 0; 56 }
C题 区间加:https://ac.nowcoder.com/acm/contest/4911/C
思路:
着实坑,首先要知道第一个数如果不是 m - 1 或 m ,答案就为 0;因为第一个数只能加 1 次。
可以类比为括号,左括号表示操作区间的左端点,右括号表示操作区间的右端点,则一个位置不能出现两个及以上的左括号或右括号。
那么对于任意一个位置都有四种情况:
1. 没有括号;
2.只有左括号;
3.只有右括号;
4.同时包含左括号和右括号。
那么我们可以设 f [ i ] [ j ] 为前 i 个位置,左括号比右括号多 j 个情况下并且 [1, i] 的区间中任意一个数等于 m 的方案数。
对于不放括号:f [ i ] [ j ] += f [ i - 1] [ j ];
对于只有左括号:f [ i ] [ j ] += f [ i - 1] [ j - 1];
对于只有右括号:f [ i ] [ j ] += ( j + 1) * f [ i - 1] [ j + 1];
(在第 i 个位置放了右括号后,左括号还比右括号多 j 个,则 [ 1, i - 1]里左括号比右括号多 j + 1 个。乘上 j + 1 是因为给这个右括号可以匹配的左括号有 j + 1 个);
对于同时包含左括号和右括号:f [ i ] [ j ] += ( j + 1) * f [ i - 1] [ j ] ; (道理同上);
其中情况 1 和 情况 2 ,a [ i ] 都加 了 j
情况 3 和 情况 4 , a [ i ] 都加了 j - 1
最后答案即为 f [ n ] [ 0 ];
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int MOD = 998244355, N = 2e3 + 10; 4 typedef long long ll; 5 6 int n, m, a[N]; 7 ll f[N][N]; 8 9 int main() 10 { 11 cin >> n >> m; 12 for(int i = 1; i <= n; i ++) scanf("%d", &a[i]); 13 14 // 初始化 如果不是这两种情况,即为 0; 15 if(a[1] == m || a[1] == m - 1) f[1][0] = 1; 16 if(a[1] == m - 1) f[1][1] = 1; 17 18 for(int i = 2; i <= n; i ++) 19 for(int j = max(0, m - a[i] - 1); j <= max(i, m - a[i]); j ++) 20 { 21 if(a[i] + j == m) 22 { 23 f[i][j] = (f[i][j] + f[i - 1][j]) % MOD; 24 if(j) f[i][j] = (f[i][j] + f[i - 1][j - 1]) % MOD; 25 } 26 27 if(a[i] + j + 1 == m) 28 { 29 f[i][j] = (f[i][j] + ll(j + 1) * f[i - 1][j + 1]) % MOD; 30 f[i][j] = (f[i][j] + ll(j + 1) * f[i - 1][j]) % MOD; 31 } 32 } 33 34 // f[n][0] = (f[n][0] + MOD) % MOD; 35 36 cout << f[n][0] << endl; 37 38 39 40 return 0; 41 }
D 题 多元组:https://ac.nowcoder.com/acm/contest/4911/D
思路:
对于三元组的情况,任意一个数的贡献是 左边比他小的 * 右边比他大的,我们是用树状数解决的, 所以这道题就是在三元组上面的拓展;
先可以得到递推式:$f[i][j] = \sum\limits_{k=1}^{i-1}f[k][j-1](a_k<a_i)$
显然我们至少需要 M 个 树状数组用来求和。
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 using namespace std; 6 const int N = 1e5 + 10, M = 60, MOD = 1e9 + 7; 7 typedef long long ll; 8 9 int n, m; 10 int a[N], b[N], f[N][M], tr[M][N]; 11 12 int lowbit(int x) { return x & -x; } 13 14 ll query(int *tr, int x) 15 { 16 ll res = 0; 17 while(x) 18 { 19 res += tr[x]; 20 x -= lowbit(x); 21 res %= MOD; 22 } 23 return res; 24 } 25 26 void add(int *tr, int x, int c) 27 { 28 while(x <= n) 29 { 30 tr[x] += c; 31 tr[x] %= MOD; 32 x += lowbit(x); 33 } 34 } 35 36 37 int main() 38 { 39 cin >> n >> m; 40 for(int i = 1; i <= n; i ++) { scanf("%d", &a[i]); b[i] = a[i]; } 41 sort(b + 1, b + n + 1); 42 int len_b = unique(b + 1, b + n + 1) - b - 1; 43 for(int i = 1; i <= n; i ++) 44 a[i] = lower_bound(b + 1, b + len_b + 1, a[i]) - b; 45 46 for(int i = 1; i <= n; i ++) f[i][1] = 1; 47 48 ll ans = 0; 49 for(int i = 1; i <= n; i ++) 50 { 51 for(int j = 2; j <= m; j ++) 52 f[i][j] = query(tr[j - 1], a[i] - 1); // a[i]-1即是小与a[i]的 53 for(int j = 1; j <= m; j ++) 54 add(tr[j], a[i], f[i][j]); 55 ans += f[i][m]; 56 ans %= MOD; 57 } 58 59 cout << ans << endl; 60 61 return 0; 62 }