本文是 AtCoder Beginner Conteset(ABC)378 的总结笔记。
A - Pairing
Problem:A - Pairing
省略。
#include <bits/stdc++.h> using namespace std;typedef long long LL;typedef pair<int , int > PII;int f[4 ];void solve () { for (int i = 0 ; i < 4 ; i++) { int c; cin >> c; f[c]++; } int res = 0 ; for (int i = 1 ; i <= 4 ; i++) res += f[i] / 2 ; cout << res << endl; } int main () { cin.tie (0 ); ios_base::sync_with_stdio (false ); solve (); return 0 ; }
B - Garbage Collection
Problem:B - Garbage Collection
模拟题。
省略。
#include <bits/stdc++.h> using namespace std;typedef long long LL;typedef pair<int , int > PII;const int N = 110 ;int q[N], r[N];int n, Q;void solve () { cin >> n; for (int i = 1 ; i <= n; i++) cin >> q[i] >> r[i]; cin >> Q; for (int i = 1 , t, d; i <= Q; i++) { cin >> t >> d; int tmp = d % q[t]; if (tmp <= r[t]) cout << d + (r[t] - tmp) << endl; else cout << d + q[t] - tmp + r[t] << endl; } } int main () { cin.tie (0 ); ios_base::sync_with_stdio (false ); solve (); return 0 ; }
C - Repeating
Problem:C - Repeating
STL
输出一个数字前面,离他最近的相同数字的位置。没有就输出 -1。
思路非常的简单,使用一个 unordered_map 来存储每个数字的出现位置。
#include <bits/stdc++.h> using namespace std;typedef long long LL;typedef pair<int , int > PII;int n, a;unordered_map<int , int > q; void solve () { cin >> n; for (int i = 1 ; i <= n; i++) { cin >> a; if (!q.count (a)) cout << -1 << " " ; else cout << q[a] << " " ; q[a] = i; } } int main () { cin.tie (0 ); ios_base::sync_with_stdio (false ); solve (); return 0 ; }
D - Count Simple Paths
Problem:D - Count Simple Paths
DFS。
一道非常标准的 DFS 题目。难度不大。
#include <bits/stdc++.h> using namespace std;typedef long long LL;typedef pair<int , int > PII;const int N = 15 ;string g[N]; bool st[N][N]; int n, m, k;int res;int dx[4 ] = {-1 , 0 , 1 , 0 }, dy[4 ] = {0 , 1 , 0 , -1 };void dfs (int x, int y, int k) { if (k == 0 ) { res++; return ; } for (int i = 0 ; i < 4 ; i++) { int a = x + dx[i], b = y + dy[i]; if (a < 1 || a > n || b < 1 || b > m) continue ; if (g[a][b] == '#' || st[a][b]) continue ; st[a][b] = true ; dfs (a, b, k - 1 ); st[a][b] = false ; } } void solve () { cin >> n >> m >> k; for (int i = 1 ; i <= n; i++) { cin >> g[i]; g[i] = " " + g[i]; } for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= m; j++) { if (g[i][j] == '#' ) continue ; st[i][j] = true ; dfs (i, j, k); st[i][j] = false ; } cout << res << endl; } int main () { cin.tie (0 ); ios_base::sync_with_stdio (false ); solve (); return 0 ; }
E - Mod Sigma Problem
Problem:E - Mod Sigma Problem
推公式 + 树状数组BIT
题目:
给定一组长度为 N N N 的序列 ( A 1 , A 2 , . . . , A N ) (A_1 , A_2 , ... , A_N) ( A 1 , A 2 , ... , A N ) ,给定一个正整数 M M M 。
求 ∑ 1 ≤ l ≤ r ≤ N ( ( ∑ l ≤ i ≤ r A i ) m o d M ) \sum_{1 \leq l \leq r \leq N} \left( \left(\sum_{l \leq i \leq r} A_i\right) \mathbin{\mathrm{mod}} M \right) ∑ 1 ≤ l ≤ r ≤ N ( ( ∑ l ≤ i ≤ r A i ) mod M ) 。
约束条件:
1 ≤ N ≤ 2 × 10 5 1 \leq N \leq 2 \times 10^5 1 ≤ N ≤ 2 × 1 0 5
1 ≤ M ≤ 2 × 10 5 1 \leq M \leq 2 \times 10^5 1 ≤ M ≤ 2 × 1 0 5
0 ≤ A i ≤ 10 9 0 \leq A_i \leq 10^9 0 ≤ A i ≤ 1 0 9
思路 :
本题整体需要解决下面两个问题:
问题 1:公式化简
我们定义带模前缀和 为 S i = ( A 1 + A 2 + . . . + A i ) m o d M S_i = (A_1+A_2+...+A_i) \bmod M S i = ( A 1 + A 2 + ... + A i ) mod M
那么我们就可以将原公式转化为 ∑ 1 ≤ l ≤ r ≤ N ( ( ∑ l ≤ i ≤ r A i ) m o d M ) = ∑ 1 ≤ l ≤ r ≤ N ( S r − S l − 1 ) m o d M \sum_{1 \leq l \leq r \leq N} \left( \left(\sum_{l \leq i \leq r} A_i\right) \mathbin{\mathrm{mod}} M \right) = \sum_{1 \leq l \leq r \leq N}(S_r - S_{l-1}) \bmod M ∑ 1 ≤ l ≤ r ≤ N ( ( ∑ l ≤ i ≤ r A i ) mod M ) = ∑ 1 ≤ l ≤ r ≤ N ( S r − S l − 1 ) mod M
根据常用的知识点:
( S r − S l − 1 ) m o d M = S r − S l − 1 + { 0 ( S l − 1 ≤ S r ) M ( S l − 1 > S r ) (S_r - S_{l-1}) \mathbin{\mathrm{mod}} M = S_r - S_{l-1} + \begin{cases} 0 & (S_{l-1} \leq S_r) \\ M & (S_{l-1} > S_r)\end{cases} ( S r − S l − 1 ) mod M = S r − S l − 1 + { 0 M ( S l − 1 ≤ S r ) ( S l − 1 > S r ) ,我们就可以将 mod 符号从公式中去除掉,原公式转化为:
∑ r = 1 N ∑ l = 1 r ( S r − S l − 1 ) m o d M = ∑ r = 1 N ( S r × r − ∑ l = 1 r S l − 1 + M × X r ) \sum_{r=1}^N \sum_{l=1}^r (S_r - S_{l-1}) \mathbin{\mathrm{mod}} M = \sum_{r=1}^N \left( S_r \times r - \sum_{l=1}^r S_{l-1} + M \times X_r \right) r = 1 ∑ N l = 1 ∑ r ( S r − S l − 1 ) mod M = r = 1 ∑ N ( S r × r − l = 1 ∑ r S l − 1 + M × X r )
备注:照理说前缀和中,S i S_i S i 是单调递增的,为什么会出现逆序对呢?
本题中使用的是带模前缀和,所以真实的 S i S_i S i 值会呈现周期性的变化,当超过 M M M 时候,又会跳回到一个比较小的数值范围,所以就可能出现 S i > S j , ( i < j ) S_i > S_j, (i < j) S i > S j , ( i < j ) 的情况出现,就对应了本题中需要加入一个偏置 M 把结果“扳正”的情况。
从上面公式可以发现,公式最后可以被拆成 3 个部分:
∑ r = 1 N ( S r × r ) \sum_{r=1}^N(S_r \times r) ∑ r = 1 N ( S r × r ) :直接使用预处理好的带模前缀和计算,时间复杂度为 O ( N ) O(N) O ( N )
− ∑ r = 1 N ∑ l = 1 r S l − 1 - \sum_{r=1}^N\sum_{l=1}^r S_{l-1} − ∑ r = 1 N ∑ l = 1 r S l − 1 :类似于前缀和的前缀和,拆开之后可以发现下图规律。化简结果为:− ∑ l = 1 N ( S l − 1 × ( N + 1 − l ) ) - \sum_{l=1}^N (S_{l-1}\times (N+1-l)) − ∑ l = 1 N ( S l − 1 × ( N + 1 − l )) 。时间复杂度 O ( N ) O(N) O ( N ) 。
M × ∑ r = 1 N X r M\times \sum_{r=1}^N X_r M × ∑ r = 1 N X r :问题就转化为,如何快速求出带模前缀和 s 的所有逆序对数量? 使用树状数组 BIT 来解决。
问题 2:使用 BIT 求逆序对
逆序对的定义是:找到所有的 ( i , j ) (i,j) ( i , j ) 使得 i < j i < j i < j 并且 a [ i ] > a [ j ] a[i] > a[j] a [ i ] > a [ j ] 。
如何使用 BIT 求逆序对的数量呢?
假设从左向右遍历数组,当处理到 s [ i ] s[i] s [ i ] 的时候,数组 s [ 1 ] , s [ 2 ] , . . . , s [ i − 1 ] {s[1] , s[2] , ... , s[i-1]} s [ 1 ] , s [ 2 ] , ... , s [ i − 1 ] 都已经被放入了 BIT 中。将数字 s [ i ] s[i] s [ i ] 作为 index,将出现次数作为 BIT 内存的值。我们可以很容易就求出,已经存入的数字中,比 s [ i ] s[i] s [ i ] 小的数字有 q u e r y ( s [ i ] ) query(s[i]) q u er y ( s [ i ]) 个。相应的,比 s [ i ] s[i] s [ i ] 大的数字数量为 i − 1 − q u e r y ( s [ i ] ) i-1-query(s[i]) i − 1 − q u er y ( s [ i ])
这一步的时间复杂度为 O ( N log N ) O(N\log N) O ( N log N )
总结:
本题的整体思路为:首先将公式进行推到化简,可以发现分别求出三个部分的值就可以得出最终的结果。最麻烦的是如何求整体的逆序对数量,这里使用了 BIT 的数据结构。
整体的时间复杂度为 O ( 2 × N + N log N ) O(2\times N + N\log N) O ( 2 × N + N log N ) 。
#include <bits/stdc++.h> using namespace std;typedef long long LL;typedef pair<int , int > PII;const int N = 2e5 + 10 ;int n, m;int a[N]; int s[N]; int tr[N]; int lowbit (int x) { return x & -x; } void add (int x, int k) { x++; for (int i = x; i <= N; i += lowbit (i)) tr[i] += k; } int sum (int x) { x++; int res = 0 ; for (int i = x; i; i -= lowbit (i)) res += tr[i]; return res; } void solve () { cin >> n >> m; for (int i = 1 ; i <= n; i++) { cin >> a[i]; s[i] = (s[i - 1 ] + a[i]) % m; } LL res = 0 ; for (int r = 1 ; r <= n; r++) res += 1ll * s[r] * r; for (int l = 1 ; l <= n; l++) res -= 1ll * s[l - 1 ] * (n - l + 1 ); LL cnt = 0 ; for (int i = 1 ; i <= n; i++) { cnt += (i - 1 - sum (s[i])); add (s[i], 1 ); } res += 1ll * m * cnt; cout << res << endl; } int main () { cin.tie (0 ); ios_base::sync_with_stdio (false ); solve (); return 0 ; }
F - Add One Edge 2
Problem:F - Add One Edge 2
题目:
给定一个树,有 N 个节点,N-1 条边。
给树增加一条边,能把树变成恰好带一个环的图。
请问有多少个图满足条件:简单图,环上的所有顶点读数为 3。
约束条件:
3 ≤ N ≤ 2 × 10 5 3 \leq N \leq 2 \times 10^5 3 ≤ N ≤ 2 × 1 0 5
1 ≤ u i , v i ≤ N 1 \leq u_i, v_i \leq N 1 ≤ u i , v i ≤ N
思路:
这道题目结论比较暴力:
直接找度数为 3 3 3 的连通块,然后向外找度数为 2 2 2 的点。假设度数为 2 2 2 的点数量为 c n t cnt c n t ,那么这些度数为 2 2 2 的点直接两两相连,都能形成题目要求的环。形成环的数量为 c n t × ( c n t − 1 ) / 2 cnt\times (cnt-1) / 2 c n t × ( c n t − 1 ) /2
由于是把所有的点都看过,所以极限状态下的时间复杂度为 O ( 2 N ) O(2N) O ( 2 N ) 。
#include <bits/stdc++.h> using namespace std;typedef long long LL;typedef pair<int , int > PII;const int N = 2e5 + 10 , M = 4e5 + 10 ;int h[N], e[M], ne[M], idx; int n;int deg[N]; bool st[N]; int cnt; LL res; void add (int a, int b) { e[idx] = b; ne[idx] = h[a]; h[a] = idx++; } void dfs (int now, int fa = 0 ) { if (deg[now] != 3 ) { cnt += (deg[now] == 2 ); return ; } st[now] = true ; for (int i = h[now]; i != -1 ; i = ne[i]) { int next = e[i]; if (next == fa) continue ; dfs (next, now); } } void solve () { memset (h, -1 , sizeof h); cin >> n; for (int i = 1 ; i <= n - 1 ; i++) { int u, v; cin >> u >> v; add (u, v), add (v, u); deg[u]++, deg[v]++; } for (int i = 1 ; i <= n; i++) { if (deg[i] != 3 ) continue ; if (st[i]) continue ; cnt = 0 ; dfs (i); res += 1ll * cnt * (cnt - 1 ) / 2 ; } cout << res << endl; } int main () { cin.tie (0 ); ios_base::sync_with_stdio (false ); solve (); return 0 ; }