2020牛客多校训练赛第二场合集
- All with Pairs
- Boundary
- Cover the Tree
- Duration
- Exclusive OR
- Fake Maxpooling
- Greater and Greater
- Happy Triangle
- Interval
- Just Shuffle
- Keyboard Free
A、All with Pairs
题意:
给出$n$个串,定义$f(s,t)$是$s$的前缀和$t$的后缀的最长相同的长度,求$\sum \limits _{1\leq i\leq j\leq n}f(s_i,s_j)$。
题解:
看到前缀长度等于后缀长度,想到什么?对,就是$kmp$的$next$数组,但是这里只要最长的,所以对于比较短的相同前缀,就需要去重。但是我们暂时还不知道怎么去重,然后我们需要快速地找这些前缀后缀,使用哈希就很棒,为了防卡,使用双哈希就很棒。然后先把所有的字符串的后缀插到哈希表,然后对每一个串进行前缀查询,找到和这个串的前缀相同的哈希值的后缀的数量,然后考虑去重,比如,需要去重的是$"aba"$中的$"a"$子串,因为字符串枚举前缀的时候,是从大到小枚举,所以就考虑碰到一个前缀$j$,就减去这个串已经统计的部分。
AC代码:
1 #include <bits/stdc++.h> 2 #include <unordered_map> 3 using namespace std; 4 const int N = 1e5 + 5; 5 const int M = 1e6 + 5; 6 typedef long long ll; 7 string str[N]; 8 int nxt[M]; 9 ll hmod[2] = {998244353, 1000000007}, base[2] = {131, 37}; 10 ll hs[2][M], f[2][M]; 11 ll vis[M]; 12 struct p_hash 13 { 14 template <class T1, class T2> 15 size_t operator()(const pair<T1, T2> &p) const 16 { 17 return hash<T1>{}(p.first) ^ hash<T2>{}(p.second); 18 } 19 }; 20 unordered_map<pair<ll, ll>, ll, p_hash> mp; 21 void init() 22 { 23 f[0][0] = f[1][0] = 1; 24 for (int i = 0; i <= 1; ++i) 25 for (int j = 1; j < M; ++j) 26 f[i][j] = f[i][j - 1] * base[i] % hmod[i]; 27 } 28 ll get_hash(int l, int r, int j) 29 { 30 return (hs[j][r] - hs[j][l - 1] * f[j][r - l + 1] % hmod[j] + hmod[j]) % hmod[j]; 31 } 32 void get_next(const string &str) 33 { 34 nxt[0] = -1; 35 int j = -1, k = 0; 36 while (k < str.size()) 37 { 38 if (j == -1 || str[j] == str[k]) 39 nxt[++k] = ++j; 40 else 41 j = nxt[j]; 42 } 43 } 44 const ll mod = 998244353; 45 int main() 46 { 47 init(); 48 ios::sync_with_stdio(0); 49 cin.tie(0); 50 int n; 51 cin >> n; 52 for (int i = 1; i <= n; ++i) 53 cin >> str[i]; 54 for (int i = 1; i <= n; ++i) 55 { 56 pair<ll, ll> tmp; 57 int len = str[i].size(); 58 for (int j = 0; j <= 1; ++j) 59 for (int k = 0; k < len; ++k) 60 hs[j][k + 1] = (hs[j][k] * base[j] % hmod[j] + str[i][k] - 'a' + 1) % hmod[j]; 61 for (int j = 1; j <= len; ++j) 62 { 63 tmp.first = get_hash(j, len, 0); 64 tmp.second = get_hash(j, len, 1); 65 ++mp[tmp]; 66 } 67 } 68 ll ans = 0; 69 for (int i = 1; i <= n; ++i) 70 { 71 get_next(str[i]); 72 int len = str[i].size(); 73 memset(vis, 0, sizeof(vis[0]) * (str[i].size() + 1)); 74 for (int j = 0; j <= 1; ++j) 75 for (int k = 0; k < len; ++k) 76 hs[j][k + 1] = (hs[j][k] * base[j] % hmod[j] + str[i][k] - 'a' + 1) % hmod[j]; 77 pair<ll, ll> tmp; 78 for (int j = len; j; --j) 79 { 80 tmp.first = get_hash(1, j, 0); 81 tmp.second = get_hash(1, j, 1); 82 ans = (ans + (mp[tmp] - vis[j] + mod) % mod * j % mod * j % mod) % mod; 83 int tnxt = nxt[j]; 84 vis[tnxt] = (vis[tnxt] + mp[tmp]) % mod; 85 } 86 } 87 cout << ans << endl; 88 return 0; 89 }
B、Boundary
题意:
给出一些点,问最多有几个点和$(0,0)$共一个圆?$n\leq 2e3$。
题解:
显然不能$O(n^3)$暴力枚举点对。所以我们就只能枚举一个点,然后因为$(0,0)$也在圆上,然后对于第三个点,它们在一个圆上,圆周角就要一样。然后直接根据向量规则求夹角的余弦值就行,但是这个题余弦值的精度差很小,所以需要对这些数乘$1e14$变成整数再存进$map$里面。如果直接算出圆心的位置,对精度的要求就会小一些。
AC代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int N = 2e3 + 5; 4 unordered_map<long long, int> mp; 5 pair<int, int> p[N]; 6 pair<int, int> operator-(const pair<int, int> &a, const pair<int, int> &b) 7 { 8 return make_pair(a.first - b.first, a.second - b.second); 9 } 10 double dist(const pair<int, int> &vec) 11 { 12 return sqrt(vec.first * vec.first + vec.second * vec.second); 13 } 14 double dotcos(const pair<int, int> &a, const pair<int, int> &b) 15 { 16 return (a.first * b.first + a.second * b.second) / (dist(a) * dist(b)); 17 } 18 int cross(const pair<int, int> &a, const pair<int, int> &b) 19 { 20 return a.first * b.second - b.first * a.second; 21 } 22 int main() 23 { 24 int n; 25 scanf("%d", &n); 26 for (int i = 1; i <= n; ++i) 27 scanf("%d%d", &p[i].first, &p[i].second); 28 int maxn = 0; 29 for (int i = 1; i <= n; ++i) 30 { 31 mp.clear(); 32 for (int j = 1; j <= n; ++j) 33 if (cross(p[i], p[j]) < 0) 34 ++mp[dotcos(p[j] - make_pair(0, 0), p[j] - p[i]) * 1e14]; 35 for (auto &j : mp) 36 maxn = max(maxn, j.second); 37 } 38 printf("%d\n", maxn + 1); 39 return 0; 40 }
C、Cover the Tree
题意:
给出一棵树,每次找两个点连路径,求最小需要连多少路径使得树上每条边都覆盖。
题解:
显然都连叶子最优。自己写的时候考虑了类似于树的重心,找到了叶节点的重心,但是实现太垃圾,一直不是$wa$就是$tle$。
看了正解才发现真的简单。直接看代码就行,正确性:设$s$是叶子节点个数,所有前$s/2$和后$s/2$个叶子结点往上的所有边都会被覆盖,这样子不可能找到一棵树,还有未被覆盖的边。
AC代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int N = 2e5 + 5; 4 typedef long long ll; 5 vector<int> G[N], lef; 6 void dfs(int u, int fa) 7 { 8 if (G[u].size() == 1) 9 { 10 lef.push_back(u); 11 return; 12 } 13 for (auto i : G[u]) 14 if (i != fa) 15 dfs(i, u); 16 } 17 void solve() 18 { 19 int n; 20 scanf("%d", &n); 21 for (int i = 1; i < n; ++i) 22 { 23 int u, v; 24 scanf("%d%d", &u, &v); 25 G[u].push_back(v); 26 G[v].push_back(u); 27 } 28 if (n == 1) 29 { 30 printf("0\n"); 31 return; 32 } 33 if (n == 2) 34 { 35 printf("1\n1 2\n"); 36 return; 37 } 38 int rt = 0; 39 for (int i = 1; i <= n; ++i) 40 if (G[i].size() != 1) 41 { 42 rt = i; 43 break; 44 } 45 dfs(rt, 0); 46 printf("%d\n", (lef.size() + 1) / 2); 47 for (int i = 0; i < lef.size() / 2; ++i) 48 printf("%d %d\n", lef[i], lef[i + (lef.size() + 1) / 2]); 49 if (lef.size() % 2) 50 printf("%d %d\n", lef[lef.size() / 2], rt); 51 } 52 int main() 53 { 54 int T = 1; 55 //scanf("%d", &T); 56 while (T--) 57 solve(); 58 return 0; 59 }
D、Duration
题意:
太简单了不说了。
题解:
随便减一下取个绝对值就行。
AC代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int N = 1e5 + 5; 4 typedef long long ll; 5 char s[10]; 6 void solve() 7 { 8 scanf("%s", s); 9 int hh = s[0] * 10 + s[1]; 10 int mm = s[3] * 10 + s[4]; 11 int ss = s[6] * 10 + s[7]; 12 int tot = hh * 3600 + mm * 60 + ss; 13 scanf("%s", s); 14 hh = s[0] * 10 + s[1]; 15 mm = s[3] * 10 + s[4]; 16 ss = s[6] * 10 + s[7]; 17 tot = hh * 3600 + mm * 60 + ss - tot; 18 printf("%d\n", abs(tot)); 19 } 20 int main() 21 { 22 int T = 1; 23 //scanf("%d", &T); 24 while (T--) 25 solve(); 26 return 0; 27 }
E、Exclusive OR
队友补。
F、Fake Maxpooling
题意:
设矩阵中$a_{ij} = lcm(i,j)$,求边长为$k$的子方阵最大值的和。$n\leq 5e3,m\leq 5e3$。
题解:
构造矩阵的时候需要利用线性筛的思路优化,虽然暴力求解卡常也能过。然后就套路题了,单调队列套单调队列。
坑点:不能$a[i][j]=a[j][i]=lcm(i,j)$,因为不保证一定是方阵,如果非要这样子,就要求完长度是$max(n,m)$的方阵。
AC代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int maxn = 5100; 5 int aa[maxn][maxn], mp[maxn][maxn], q[maxn]; 6 inline int gcd(int a, int b) 7 { 8 return !b ? a : gcd(b, a % b); 9 } 10 inline int lcm(int a, int b) 11 { 12 return a * b / gcd(a, b); 13 } 14 int main() 15 { 16 int n, m, a, b, k; 17 scanf("%d%d%d", &n, &m, &k); 18 a = k, b = k; 19 for (int i = 1; i <= n; ++i) 20 for (int j = 1; j <= m; ++j) 21 aa[i][j] = lcm(i, j); 22 for (int i = 1; i <= n; i++) 23 { 24 int l = 1, r = 0; 25 for (int j = 1; j <= b; j++) 26 { 27 while (r && aa[i][q[r]] < aa[i][j]) 28 r--; 29 q[++r] = j; 30 } 31 mp[i][1] = aa[i][q[l]]; 32 for (int j = 2; j + b - 1 <= m; j++) 33 { 34 if (q[l] == j - 1) 35 l++; 36 while (l <= r && aa[i][q[r]] < aa[i][j + b - 1]) 37 r--; 38 q[++r] = j + b - 1; 39 mp[i][j] = aa[i][q[l]]; 40 } 41 } 42 ll ans = 0; 43 for (int j = 1; j <= m; j++) 44 { 45 int l = 1, r = 0; 46 for (int i = 1; i <= a; i++) 47 { 48 while (r && mp[q[r]][j] < mp[i][j]) 49 r--; 50 q[++r] = i; 51 } 52 (ans += mp[q[l]][j]); 53 for (int i = 2; i + a - 1 <= n; i++) 54 { 55 if (q[l] == i - 1) 56 l++; 57 while (l <= r && mp[q[r]][j] < mp[i + a - 1][j]) 58 r--; 59 q[++r] = i + a - 1; 60 (ans += mp[q[l]][j]); 61 } 62 } 63 printf("%lld\n", ans); 64 return 0; 65 }
G、Greater and Greater
题意:
给出$a$和$b$序列,问$a$当中有多少个子数组,对应位置的数大小都大于$b$。
题解:
看数据范围暴力可以卡常过,所以考虑怎么卡常,$bitset$是卡常利器。所以我们就想一下怎么用$bitset$卡常,然后把$bitset$的$a_i$大于$b_j$的$i$位设成$1$,最后右移$j$位,得到合法的数的数量。然后对于每个$b$序列都这么处理,最后取与即可。实现的时候,先降序排序然后用一个$bitset$记录就行了,因为前面枚举到的数一定大于后面枚举到的。
AC代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int N = 1.5e5 + 5; 4 bitset<N> f, g; 5 struct node 6 { 7 int val, id; 8 }; 9 node a[N], b[N]; 10 int main() 11 { 12 int n, m; 13 scanf("%d%d", &n, &m); 14 for (int i = 1; i <= n; ++i) 15 scanf("%d", &a[i].val), a[i].id = i; 16 for (int i = 1; i <= m; ++i) 17 scanf("%d", &b[i].val), b[i].id = i; 18 sort(a + 1, a + n + 1, [](const node &a, const node &b) -> bool { return a.val > b.val; }); 19 sort(b + 1, b + m + 1, [](const node &a, const node &b) -> bool { return a.val > b.val; }); 20 f.set(); 21 g.reset(); 22 for (int i = 1, j = 1; i <= m; ++i) 23 { 24 while (j <= n && a[j].val >= b[i].val) 25 g.set(a[j++].id); 26 //cout << "g=" << g << endl; 27 f &= g >> b[i].id; 28 //cout << "f=" << f << endl; 29 } 30 printf("%d\n", f.count()); 31 return 0; 32 }
H、Happy Triangle
题意:
维护一个集合$s$,给出以下指令:
$1$、添加一个数
$2$、删除一个数
$3$、给出一个数,询问里面有没有两个数和这个数组成非退化三角形。
题解:
操作$1$和$2$都是小意思,康康第三个怎么整。如果$x$是最大边,这样子找两个前驱就行了,如果不是,就找两个$a$,$b$,且$a$要大于等于$x$和$b$,$a-b$小于$x$。关键是维护这个有序序列的相邻差值和求区间最小值,使用权值线段树。线段树维护三个值,区间最小值,区间最大值和区间最小的差值。对于长度是$1$的区间,如果有几个数,则差值是$0$,如果只有一个数,视为无穷大,差值的维护就是,对于一段区间,最小差值来自子区间和跨区间取得(就是右区间最小减左区间最大),取最小上推。查询同理。
AC代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int N = 4e5 + 5; 4 const int inf = 0x7fffffff; 5 int mp[N]; 6 int b[N], top; 7 struct segtree 8 { 9 #define ls (k << 1) 10 #define rs ((k << 1) + 1) 11 struct node 12 { 13 int l, r, maxn, minn, gap; 14 }; 15 node tr[N << 1]; 16 inline void pushup(int k) 17 { 18 tr[k].maxn = max(tr[ls].maxn, tr[rs].maxn); 19 tr[k].minn = min(tr[ls].minn, tr[rs].minn); 20 tr[k].gap = min(tr[ls].gap, tr[rs].gap); 21 tr[k].gap = min(tr[k].gap, tr[rs].minn - tr[ls].maxn); 22 } 23 void build(int l, int r, int k) 24 { 25 tr[k].l = l; 26 tr[k].r = r; 27 if (l == r) 28 { 29 tr[k].maxn = 0; 30 tr[k].minn = inf; 31 tr[k].gap = inf; 32 return; 33 } 34 int m = (l + r) >> 1; 35 build(l, m, ls); 36 build(m + 1, r, rs); 37 pushup(k); 38 } 39 void update(int k, int pos, int val) 40 { 41 if (tr[k].l == tr[k].r) 42 { 43 mp[pos] += val; 44 tr[k].gap = inf; 45 if (mp[pos] == 0) 46 { 47 tr[k].maxn = 0; 48 tr[k].minn = inf; 49 } 50 else 51 { 52 tr[k].maxn = tr[k].minn = b[pos]; 53 if (mp[pos] >= 2) 54 tr[k].gap = 0; 55 } 56 return; 57 } 58 int m = (tr[k].l + tr[k].r) >> 1; 59 if (pos <= m) 60 update(ls, pos, val); 61 else 62 update(rs, pos, val); 63 pushup(k); 64 } 65 int querym(int l, int r, int k, int op) 66 { 67 if (l > r) 68 return op ? 0 : inf; 69 if (l <= tr[k].l && tr[k].r <= r) 70 return op ? tr[k].maxn : tr[k].minn; 71 int m = (tr[k].l + tr[k].r) >> 1; 72 int ans1 = 0, ans2 = inf, tmp; 73 if (l <= m) 74 { 75 tmp = querym(l, r, ls, op); 76 ans1 = max(ans1, tmp); 77 ans2 = min(ans2, tmp); 78 } 79 if (r > m) 80 { 81 tmp = querym(l, r, rs, op); 82 ans1 = max(ans1, tmp); 83 ans2 = min(ans2, tmp); 84 } 85 return op ? ans1 : ans2; 86 } 87 int queryg(int l, int r, int k) 88 { 89 if (l > r) 90 return inf; 91 if (l <= tr[k].l && tr[k].r <= r) 92 return tr[k].gap; 93 int m = (tr[k].l + tr[k].r) >> 1; 94 int ans = inf; 95 int f = 0; 96 if (l <= m) 97 ans = min(ans, queryg(l, r, ls)), ++f; 98 if (r > m) 99 ans = min(ans, queryg(l, r, rs)), ++f; 100 if (f == 2) 101 ans = min(ans, tr[rs].minn - tr[ls].maxn); 102 return ans; 103 } 104 #undef ls 105 #undef rs 106 }; 107 pair<int, int> q[N]; 108 #define x first 109 #define y second 110 segtree tr; 111 bool check(int op, int pos, int val) 112 { 113 if (mp[pos] >= 2) 114 return 1; 115 else if (mp[pos] == 1) 116 { 117 int maxn = tr.querym(1, pos - 1, 1, 1); 118 if (maxn != 0) 119 return 1; 120 int ming = tr.queryg(pos, top, 1); 121 if (ming < val) 122 return 1; 123 return 0; 124 } 125 int maxn = tr.querym(1, pos, 1, 1); 126 int minn = tr.querym(pos, top, 1, 0); 127 if (maxn != 0 && minn != inf && maxn + val > minn) 128 return 1; 129 int mpos = lower_bound(b + 1, b + top + 1, maxn) - b; 130 if (maxn) 131 { 132 tr.update(1, mpos, -1); 133 int smaxn = tr.querym(1, pos, 1, 1); 134 tr.update(1, mpos, 1); 135 if (smaxn && smaxn + maxn > val) 136 return 1; 137 } 138 int ming = tr.queryg(pos, top, 1); 139 if (ming < val) 140 return 1; 141 return 0; 142 } 143 int main() 144 { 145 int n; 146 scanf("%d", &n); 147 for (int i = 1; i <= n; ++i) 148 { 149 scanf("%d%d", &q[i].x, &q[i].y); 150 b[++top] = q[i].y; 151 } 152 sort(b + 1, b + top + 1); 153 top = unique(b + 1, b + top + 1) - b - 1; 154 for (int i = 1; i <= n; ++i) 155 q[i].y = lower_bound(b + 1, b + top + 1, q[i].y) - b; 156 tr.build(1, top, 1); 157 for (int i = 1; i <= n; ++i) 158 { 159 if (q[i].x == 1) 160 tr.update(1, q[i].y, 1); 161 else if (q[i].x == 2) 162 tr.update(1, q[i].y, -1); 163 else 164 printf("%s", check(q[i].x, q[i].y, b[q[i].y]) ? "Yes\n" : "No\n"); 165 } 166 return 0; 167 }
I、Interval
队友补。
J、Just Shuffle
队友补
AC代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int N = 1e5 + 5; 4 int a[N], b[N]; 5 vector<int> v; 6 bool vis[N]; 7 int main() 8 { 9 int n, m; 10 scanf("%d%d", &n, &m); 11 for (int i = 1; i <= n; ++i) 12 scanf("%d", &a[i]); 13 for (int i = 1; i <= n; ++i) 14 if (!vis[i]) 15 { 16 v.clear(); 17 int x = a[i]; 18 while (!vis[x]) 19 { 20 vis[x] = 1; 21 v.push_back(x); 22 x = a[x]; 23 } 24 int inv, r = v.size(); 25 for (int i = 0; i < r; ++i) 26 if (1ll * m * i % r == 1) 27 inv = i; 28 for (int i = 0; i < r; ++i) 29 b[v[i]] = v[(i + inv) % r]; 30 } 31 for (int i = 1; i <= n; ++i) 32 printf("%d%c", b[i], " \n"[i == n]); 33 return 0; 34 }
K、Keyboard Free
队友补。