Codeforces Round 1005 (Div. 2)
题意
给定01序列s s s 和空序列t t t ,你每次可以将s s s 或t t t 的一个后缀移动到另一个序列的后面,求最少几次操作可以使s s s 全为0 0 0 而t t t 全为1 1 1 。
思路
首先可以发现连续的0 0 0 和1 1 1 是可以整体操作的,所以可以简化s s s 的形式为01交替序列。
其次,s s s 的前缀0 0 0 和t t t 的前缀1 1 1 是不需要操作的,于是我们可以直接删去它们。这样我们的任务变成了求最小次数使s s s 和t t t 都为空。
如果称简化01交替序列长度为复杂度的话,每次操作复杂度至多减1,也就是说最少要简化原序列的复杂度次才能消完。
构造一种消法:每次把s s s 所有都放到t t t ,或者t t t 所有都放到s s s (前缀0 0 0 和1 1 1 不动),这样就可以每次减少一个复杂度。
故答案就是01交替的次数+ 1 +1 + 1 (去掉前缀0 0 0 ),时间复杂度O ( n ) O(n) O ( n ) 。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <bits/stdc++.h> using namespace std;const int N = 1e3 + 1 ;inline int readint () { int x = 0 ; char ch = getchar (); while (ch < '0' || ch > '9' ) ch = getchar (); while (ch >= '0' && ch <= '9' ) x = x * 10 + (ch - '0' ), ch = getchar (); return x; } inline bool readbool () { char ch = getchar (); while (ch != '0' && ch != '1' ) ch = getchar (); return ch == '1' ; } signed main () { int t = readint (); while (t--){ int n = readint (); bool pre = 0 ; int sum = 0 ; while (n--){ bool x = readbool (); if (x != pre) sum += 1 , pre = x; } printf ("%d\n" , sum); } return 0 ; }
题意
删去最长的子串,使不同的数的数量最少。
思路
找到最长不重子串即可。时间复杂度O ( n ) O(n) O ( n ) 。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <bits/stdc++.h> using namespace std;const int N = 2e5 + 1 ;int n, A[N], B[N];signed main () { int t; cin >> t; while (t--){ cin >> n; memset (B, 0 , sizeof (B)); for (int i = 1 ; i <= n; ++i){ cin >> A[i]; B[A[i]]++; } int maxlen = 0 , maxl = 0 , curlen = 0 , curl = 0 ; for (int i = 1 ; i <= n; ++i){ if (B[A[i]] == 1 ){ if (!curlen){ curlen = 1 , curl = i; }else { curlen ++; } }else { curlen = 0 ; } if (curlen > maxlen){ maxlen = curlen; maxl = curl; } } if (maxlen == 0 ){ cout << 0 << endl; }else { cout << maxl << " " << maxl + maxlen - 1 << endl; } } return 0 ; }
题意
给定一个序列,每次选择一个数,得分加上这个数的绝对值;如果这个数小于0 0 0 ,则删掉这个数和它右边的数;如果这个数大于0 0 0 ,则删掉这个数和它左边的数。求得分最大值。
思路
枚举最后取的数,那么这个数左边一定只能取正数,右边一定只能取负数。左边的正数从左往右取,右边的负数从右往左取,则可以取走左边所有正数和右边所有负数。这样得分就是最大化的,枚举所有数并取最大值即可。
时间复杂度O ( n ) O(n) O ( n )
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <bits/stdc++.h> typedef long long LL;using namespace std;const int N = 2e5 + 2 ;int n, A[N];LL presum[N], sufsum[N]; signed main () { int t; cin >> t; while (t--){ cin >> n; presum[0 ] = sufsum[n + 1 ] = 0 ; for (int i = 1 ; i <= n; ++i){ cin >> A[i]; presum[i] = presum[i - 1 ]; if (A[i] > 0 ) presum[i] += A[i]; } for (int i = n; i >= 1 ; --i){ sufsum[i] = sufsum[i + 1 ]; if (A[i] < 0 ) sufsum[i] -= A[i]; } LL ans = 0 ; for (int i = 1 ; i <= n; ++i){ ans = max (ans, presum[i - 1 ] + sufsum[i + 1 ] + abs (A[i])); } cout << ans << endl; } return 0 ; }
题意
给定一个序列,每次询问假设有一个数x x x ,从右往左和序列中的数比较,如果x x x 大于等于当前数,则x x x 和当前数作异或赋值给x x x ,继续判断下一个数;如果x x x 打败所有数或者x x x 小于当前数,游戏结束,得分为x x x 打败的数的数量。每次询问求得分。
思路
显然x x x 被打败当且仅当在某一次比较中,当前数的最高若干位和x x x 相等且某一位比x x x 大(x x x 在这一位为0 0 0 ,而那个数是1 1 1 )。
我们从高位往低位枚举每一个二进制位,并维护两个指针:l e f t left l e f t 和r i g h t right r i g h t ,表示x x x 在l e f t left l e f t 这一点必败,在r i g h t right r i g h t 以及右边必胜。
一开始l e f t = 0 , r i g h t = n + 1 left = 0, right = n + 1 l e f t = 0 , r i g h t = n + 1 ,枚举到每一位的时候,我们尝试缩小这个区间:如果x x x 在这一位是1 1 1 ,则在[ l e f t + 1 , r i g h t − 1 ] [left + 1, right - 1] [ l e f t + 1 , r i g h t − 1 ] 这个区间内,对于从右往左数第一个1 1 1 右边的数,x x x 都是必胜的,而对于从右往左数第二个1 1 1 ,x x x 就必败了;如果x x x 在这一位是0 0 0 ,则在区间内,对于从右往左数第一个1 1 1 ,x x x 就必败了。这样可以缩小这个区间,最后答案就是n − l e f t n - left n − l e f t 。复杂度可以通过预处理达到O ( q log n ) O(q\log n) O ( q log n ) ,代码中展现的做法是O ( q log 2 n ) O(q\log^2 n) O ( q log 2 n ) 的。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <bits/stdc++.h> using namespace std;const int N = 2e5 + 2 , M = 30 ;int n, q, A[N], rxor[N];set<int , greater<int > > st[M]; signed main () { int t; cin >> t; while (t--){ cin >> n >> q; for (int i = 1 ; i <= n; ++i){ cin >> A[i]; } for (int j = 0 ; j < M; ++j) st[j].clear (); rxor[n + 1 ] = 0 ; for (int i = n; i >= 1 ; --i){ rxor[i] = rxor[i + 1 ] ^ A[i]; for (int j = 0 ; j < M; ++j){ if ((1 << j) & A[i]){ st[j].insert (i); } } } while (q--){ int x; cin >> x; int defeat = 0 , win = n + 1 ; for (int j = M - 1 ; j >= 0 ; --j){ auto it = st[j].upper_bound (win); int fi = 0 , se = 0 ; if (it != st[j].end ()){ fi = *it; ++it; if (it != st[j].end ()){ se = *it; } } if ((x ^ rxor[win]) & (1 << j)){ if (se){ defeat = max (defeat, se); } win = max (defeat + 1 , fi + 1 ); }else { if (fi){ defeat = max (defeat, fi); } } } cout << n - defeat << ' ' ; } cout << endl; } return 0 ; }
题意
见原题,不好描述。
思路
初始答案为1 1 1 ,从左往右逐次删去最短的一条,我们可以发现,每次删去一条之后,记这条周围与它颜色相同的所有条的个数为x x x ,则答案会乘C x x − 1 = x C_x^{x - 1} = x C x x − 1 = x 。所以把相邻条缩成一条丢到链表里面,支持删除和合并的操作,就可以得到正确答案。复杂度O ( n ) O(n) O ( n ) 。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #include <bits/stdc++.h> using namespace std;typedef long long LL;const int N = 2e5 + 1 , MOD = 998244353 ;int pre[N], nxt[N], col[N], num[N], idx;int n, p[N], c[N], bl[N];int fa[N];int find (int x) { return (fa[x] == x ? x : (fa[x] = find (fa[x]))); } signed main () { int t; cin >> t; while (t--){ cin >> n; for (int i = 1 ; i <= n; ++i){ cin >> p[i]; fa[i] = i; } int prec = -1 ; idx = 0 ; for (int i = 1 ; i <= n; ++i){ cin >> c[i]; if (c[i] != prec){ col[++idx] = c[i]; num[idx] = 1 ; pre[idx] = idx - 1 ; nxt[idx] = 0 ; if (idx != 1 ) nxt[idx - 1 ] = idx; prec = c[i]; }else { num[idx]++; } bl[p[i]] = idx; } int ans = 1 ; for (int i = 1 ; i <= n; ++i){ int now = find (bl[i]); if (num[now] != 1 ){ ans = (LL)ans * num[now] % MOD; num[now]--; }else { nxt[pre[now]] = nxt[now]; pre[nxt[now]] = pre[now]; num[now]--; now = pre[now]; while (now && nxt[now] && col[now] == col[nxt[now]]){ fa[find (nxt[now])] = find (now); now = nxt[now]; nxt[pre[now]] = nxt[now]; pre[nxt[now]] = pre[now]; num[pre[now]] += num[now]; num[now] = 0 ; now = pre[now]; } } } cout << ans << endl; } return 0 ; }
题意
给定一个序列和整数k k k ,求有多少个这个序列的连续子序列,满足可以被劈成非空的两部分,使左半部分的最小值加右半部分的最大值刚好为k k k 。
思路
首先观察到m i n ( b 1 , b 2 , ⋯ , b i ) min(b_1, b_2, \cdots, b_i) m i n ( b 1 , b 2 , ⋯ , b i ) 和m a x ( b i + 1 , b i + 2 , ⋯ , b m ) max(b_{i + 1}, b_{i + 2}, \cdots, b_m) m a x ( b i + 1 , b i + 2 , ⋯ , b m ) 随着i i i 增大都是单调不升的,说明只有唯一的整数对( x , y ) (x, y) ( x , y ) 满足x + y = k x + y = k x + y = k ,且使x = m i n ( b 1 , b 2 , ⋯ , b i ) x = min(b_1, b_2, \cdots, b_i) x = m i n ( b 1 , b 2 , ⋯ , b i ) 且y = m a x ( b i + 1 , b i + 2 , ⋯ , b m ) y = max(b_{i + 1}, b_{i + 2}, \cdots, b_m) y = m a x ( b i + 1 , b i + 2 , ⋯ , b m ) 。所以一个子序列对应一个( x , y ) (x, y) ( x , y ) ,或者不对应。这说明通过不同的( x , y ) (x, y) ( x , y ) 得到的合法子序列总是不同的(关键性质)。
对于a i + a j = k ( i < j ) a_i + a_j = k(i < j) a i + a j = k ( i < j ) ,我们定义它的左右端点的掌管区间分别为[ 上一个小于等于 a i 的数的下标 + 1 , i ] [\text{上一个小于等于}a_i\text{的数的下标} + 1, i] [ 上一个小于等于 a i 的数的下标 + 1 , i ] 和[ j , 下一个大于等于 a j 的数的下标 ] [j, \text{下一个大于等于}a_j\text{的数的下标}] [ j , 下一个大于等于 a j 的数的下标 ] 。这样可以做到不重不漏。如果可行,那么这对( i , j ) (i, j) ( i , j ) 的贡献就是这两个区间长度的乘积。
考虑何时会不可行:如果下一个小于 a i 的数的下标 \text{下一个小于}a_i\text{的数的下标} 下一个小于 a i 的数的下标 比上一个大于 a j 的数的下标 \text{上一个大于}a_j\text{的数的下标} 上一个大于 a j 的数的下标 要大的话,那( i , j ) (i, j) ( i , j ) 就无法产生贡献。所以这种情况不可行。
具体实现可以用单调栈预处理所有的“上一个小于等于”,“下一个大于等于”,“下一个小于”和“上一个大于”,然后枚举i i i ,发现j j j 的可行域是一个区间,所以可以用双指针动态维护。最后复杂度为O ( n ) O(n) O ( n ) 。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 #include <bits/stdc++.h> using namespace std;typedef long long LL;const int N = 2e5 + 1 ;int n, k, A[N], prel[N], preh[N], sufl[N], sufh[N], st[N], top;vector<int > G[N]; int head[N], tail[N], len[N]; signed main () { int t; cin >> t; while (t--){ cin >> n >> k; for (int i = 1 ; i <= n; ++i){ G[i].clear (), head[i] = 0 , tail[i] = -1 , len[i] = 0 ; } for (int i = 1 ; i <= n; ++i){ cin >> A[i]; G[A[i]].push_back (i); prel[i] = preh[i] = 0 ; sufl[i] = sufh[i] = n + 1 ; } for (int i = 1 ; i <= n; ++i){ while (top && A[st[top]] > A[i]){ sufl[st[top--]] = i; } st[++top] = i; } top = 0 ; for (int i = 1 ; i <= n; ++i){ while (top && A[st[top]] <= A[i]){ sufh[st[top--]] = i; } st[++top] = i; } top = 0 ; for (int i = n; i >= 1 ; --i){ while (top && A[st[top]] >= A[i]){ prel[st[top--]] = i; } st[++top] = i; } top = 0 ; for (int i = n; i >= 1 ; --i){ while (top && A[st[top]] < A[i]){ preh[st[top--]] = i; } st[++top] = i; } top = 0 ; LL ans = 0 ; for (int i = 1 ; i <= n; ++i){ int b = k - A[i]; if (b > n) continue ; while (head[b] < G[b].size () && G[b][head[b]] <= i){ int now = G[b][head[b]]; if (head[b] <= tail[b]){ len[b] -= sufh[now] - now; } ++head[b]; } tail[b] = max (tail[b], head[b] - 1 ); if (head[b] >= G[b].size ()) continue ; while (tail[b] + 1 < G[b].size ()){ int j = tail[b] + 1 , now = G[b][j]; if (preh[now] >= sufl[i]) break ; len[b] += sufh[now] - now; ++tail[b]; } ans += (LL) (i - prel[i]) * len[b]; } cout << ans << endl; } return 0 ; }