G. Maximize the Remaining String
由小写字母组成的字符串(s),每次从中选取重复的字母进行删除,直到该字符串中的每个字母都只出现一次。问:最终形成的字典序最大的字符串,比如(ababab),答案为(ba)。
(1 leq len(s) leq 200000)
题解
记(s=a_1a_2a_3cdots a_n),最后的字符串为(s'),它的首字符在(s)中对应位置为(p),显然(p)之前的字符都被删除了,并且被删除的字符在(p)位置之后都出现过,根据这个性质,二分枚举(p)。假设子串(a_1a_2cdots a_p)中字典序最大的字符为(a_i):
- (a_i > a_p),则字符(a_i)作为(s')的首字符,更新(s=a_{i + 1}a_{i+2}a_{i+3}cdots a_n) - ({a_i}),(减号表示删除前一个字符串中所有的后一个字符)
- (a_i == a_p),则字符(a_i)作为(s')的首字符,更新(s=a_{p + 1}a_{p+2}a_{p+3}cdots a_n) - ({a_p})
- (a_i < a_p),则字符(a_p)作为(s')的首字符,更新(s=a_{p + 1}a_{p+2}a_{p+3}cdots a_n) - ({a_p})
当(a_i > a_p)时,如果有多个(a_i),显然选择第一次出现的(a_i)更优
通过不断的枚举(p)确定(s')中的每个字符,且枚举次数不大于26次。
复杂度(O(26*n*log(n)))
E. K-periodic Garland
由0,1组成的字符串(s),如果(s)中任意相邻字符'1'之间的距离为(K),则该字符串是良好的。将 '0' ( ightarrow) '1' (或者 '1' ( ightarrow) '0') 称为一次操作,问最少几次操作后,能将(s)变成良好的?
注意:'00'、'0100' 对任意的(K)都是良好的,(1 le K le len(s) le 1e6)
题解
记最终良好的字符串为(str),考虑(s)中从左至右第一个 '1' 出现的位置,记为(pos),显然,(str)中从左至右第一个 '1' 出现的位置不会小于(pos),那么可以求得以(pos)作为(str)中从左至右第一个 '1' 出现的位置且满足良好的最少操作次数。如果不是(pos),那么会不会是(s)中第一个 '1' 和 第二个 '1' 之间的某个位置(P)呢?假设(str)满足上述条件,以(P)位置作为(str)中第一个 '1' 出现的位置,这样做的话需要 (t) 次操作,而令(s[pos] ightarrow 0),既让(str)中第二个 '1' 作为第一个 '1',所需要的操作次数至多为 (t - 1),故这样的(P)显然不应该考虑。综上所述,我们可以枚举(str)的第一个'1' 出现的位置,复杂度(O(frac{n^2}{K}))。对数论稍微敏感一点儿就会发现对(K)的加法将这些位置划分为了(K)类,什么意思呢?假设(K = 3)
(i) | |||||
---|---|---|---|---|---|
0 | 3 | 6 | 9 | 12 | 15 |
1 | 4 | 7 | 10 | 13 | 16 |
2 | 5 | 8 | 11 | 14 | 17 |
3 | 6 | 9 | 12 | 15 | 18 |
4 | 7 | 10 | 13 | 16 | 19 |
如果计算出了(pos = 0)的答案,相当于也就计算出了(pos = 3,6,9,12)的答案。所以只需要计算(K)次,每次的复杂度为(O(frac{n}{K})),故总的复杂度为(O(n))。
//#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,no-stack-protector,fast-math")
// cnt[j] := 以 j 位置作为 str 中第一个 ‘1’ 的位置且使得 s 变为 str 所需要的最少次数
#include <bits/stdc++.h>
#define IO ios_base::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int maxn = 1e6 + 5;
int t, n, k;
int main()
{
cin >> t;
while (t--)
{
cin >> n >> k;
string s;
cin >> s;
vector<int> use(2 * n + 5, 0);
vector<int> cnt(2 * n + 5, 0);
int pos = -1;
for (int i = n - 1; ~i; i--) if (s[i] == '1') {
pos = i;
break;
}
if (pos == -1) {
cout << 0 << endl;
continue;
}
for (int i = 0; i < pos; i++) cnt[i] = -1;
use[n - 1] = (s[n - 1] == '1');
for (int i = n - 2; ~i; i--) {
if (s[i] == '1') use[i] = 1;
use[i] += use[i + 1];
}
int ans = n << 1 + 1;
for (int i = n - 1; ~i; i--) if (s[i] == '1') {
int tp = 0;
for (int j = i + k; ; j += k) {
tp += (use[j - k + 1] - use[j] + 1);
if (cnt[j] != -1) {
tp = tp - 1 + cnt[j];
if (use[i + 1]) tp = min(tp, use[i + 1]); // 直接抹掉 i 位置之后的所有的 ‘1’,可能较优。例如 K = 1,s = 10001
break;
}
if (j >= n) break;
}
cnt[i] = tp;
ans = min(ans, tp + use[0] - use[i]);
}
cout << ans << endl;
/* code */
}
return 0;
}
wonderful, BFS
给一个01矩阵(A_{m*n}),有(q)次询问:(x,y),问离((x,y))最近的 1 的曼哈顿距离。
(1 leq n,m leq 1000),(1 leq q leq 1e5)
题解
加一个超级源点(s),与所有的 1 连接起来,然后以(s) 作为起点,BFS遍历图的同时更新距离即可。复杂度(O(nm))【单纯的记录一哈,orz
queue < pair<int, int> > qe;
void BFS() {
memset(d, -1, sizeof(d));
for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) if (mp[i][j]) {
d[i][j] = 0;
qe.push(make_pair(i, j));
}
while(!qe.empty()) {
pair<int, int> p = qe.front();
qe.pop();
for (int i = 0; i < 4; i++) {
int u = p.first + dx[i], v = p.second + dy[i];
if (u < 0 || v < 0 || u >= n || v >= m || d[u][v] != -1) continue;
d[u][v] = d[p.first][p.second] + 1;
qe.push(make_pair(u, v));
}
}
}
1349C - Orac and Game of Life【好题】
给一个长度为(n)的序列,每次选择一个区间([l,r])并将这个区间包含的元素都替换为该区间的中位数(按照大小关系排在中间的数)。问最终是否能将整个序列中的元素都替换为(k)?
(1 leq n leq 1e5),(1 leq a_i, k leq 1e9)
题解
-
原始序列不包含(k),显然无解
-
如果原始序列中存在一个区间,其中位数为(k),则最终肯定能够将整个序列替换为(k)。
-
如果原始序列不存在上述区间呢?构造
在原序列中直接找一个中位数为(k)的区间,想了很久都没想到一个好的办法。其实,着眼于细微之处,如果序列中存在相邻的两个数为(k,a_i)或者(a_i,k),有(a_i geq k),那么显然这是一个中位数为(k)的区间。如果不存在这样的相邻两个数,既对任意的(k),其左右相邻的两个数都小于(k)((a_i,k,a_j),都有(a_i < k),(a_j < k)),那么只需要将其中的任意一个((a_i)或者(a_j))替换为大于或者等于(k)的数就行,等价于找一个中位数大于或者等于(k)的区间。怎么找呢?【orz】其实只需要找到一个区间((a_e,a_{e+1},a_{e+2})),其中至少存在两个大于(k)的数就行。为什么只需要找长度为3区间呢?假设存在一个区间([l,r])其中位数为(p),(pgeq k),说明这个区间大于或者等于(p)的元素至少有(frac{r-l+1}{2} + 1)个,既大于或者等于(k)的数至少超过一半,显然,至少存在一个长度为3的区间其中位数必定大于或者等于(k)。证明的话,将区间划分成一个一个长度为3且不相交的区间,然后对最后剩下的一个区间分类讨论即可。如果找不到上述的长度为3且满足条件的区间,显然不可能将(a_i)或者(a_j)替换为大于或者等于(k)的数。
1349A - Orac and LCM
给一个长度为(n)的序列,求(gcd({lcm(a_i,a_j)|1 leq i,j leq n,i eq j}))
(1 leq n,a_i leq 2e5)
题解
想到了枚举最终答案的约数,但等于圈圈。记最终答案为(ans),(p)表示质数,注意:如果(p^k | ans),那么至少存在(n-1)个(a_i),有(p^k|a_i)。
反证法很好证明。知道这个特点之后怎么做呢?直接的思路依然是枚举,然而!求(n-1)个(a_i)的(gcd)不就找到了(p^k)吗,妙得一匹。记(gcd(a_i) = gcd({a} - {a_i})),那么(ans = lcm(gcd(a_1), gcd(a_2), gcd(a_3),cdots,gcd(a_n))).
1346F - Dune II: Battle For Arrakis
给一个棋盘(A_{n*m}),每个格子上有(a_{ij})枚棋子,记(cnt)为 将所有的棋子移到同一个格子上所需要的最少次数,一次只能将一枚棋子移动一步(上下左右)。现给(k)次修改:将(a_{ij} ightarrow b_{ij}),问每次修改后需要的最少次数,既(cnt)。
(1 leq n,m,k leq 1000)
题解
我的想法:研究修改后的最佳位置与先前最佳位置的关系,然并卵
记最佳位置为((x,y)),有
记(A_i = sum_{j=1}^ma_{ij}), (B_j = sum_{i=1}^na_{ij}),有
这是一个最优化问题,最小化(cnt),也就是分别最小化(sum_{i=1}^n|x-i|*A_i) 和 (sum_{j=1}^m|y-j|*B_j)。
重点来了,最小化(sum_{i=1}^n|x-i|)比较好做,在最中间任取一个数就行。那多了一部分(A_i)该怎么做喃?在脑海中抽象一个模型,(x)轴上有一些球落在(x_i)处,记(x_i)处的球的个数为(A_i),然后有一个挡板(X)也在(x)轴上,问(X)的最佳位置,使得(sum_{i=1}^n|X-x_i|*A_i)最小。本质上与最小化(sum_{i=1}^n|x-i|)是同一个问题,所以(X)的位置依然是在最中间。同样的道理可以求得(y)的位置,知道((x,y))后就可以以(O(1000))的复杂度求得(cnt),最终复杂度为(O(max(n,m)*k))
Palindrome(好题)
给一个字符串(s),问它有多少个子串是好子串?一个子串被称为好子串(S)当且仅当它的长度为(3n-2)并且(S[i] = S[2n-i] = S[2n + i - 2])((1 leq i leq n)).
(1 leq len(s) leq 500000)
题解
分析(S[i] = S[2n-i] = S[2n + i - 2]),可以发现对于一个长度为(3n-2)的好子串(S),有:
- (S_{123...(2n-1)})是一个回文串并且中心为(n),半径为(n-1);
- (S_{n(n+1)(n+2)...(3n-2)})是一个回文串并且中心为(2n-1),半径为(n-1)
枚举前一个回文串的中心和半径,那么后一个回文串的中心和半径也就确定了。先用马拉车算法预处理出每个位置的回文半径(len[pos])。考虑已经枚举到(s)的第(i)位置,该位置的回文半径为(len[i]),如果(len[i+len[i]] geq len[i]),那么一个好子串就出现了,所以需要考虑以(i)位置为中心的所有回文子串,检查是否有合法的后一个回文串,既(len[i + len[i] - t] geq len[i] - t)((0 leq t leq len[i] - 1)).
显然,不能直接暴力的检查 以(i)位置为中心的所有回文子串是否有合法的后一个回文串。观察上面这个不等式,移项:
重点来了,令(p[i] = n - i + 1),((1leq i leq n)),那么合法的后一个回文串的个数就等于在(sum_{j=i+1}^{i+len[i]}((len[j] + p[j]-p[i+len[i]])geq len[i])),移项:
求该和式等价于求一个区间([l,r])内,大于或者等于(K)的数有多少个?方法比较多,主席树,线段树,离线树状数组。
/* 树状数组 */
int n, m;
int len[maxn << 1], b[maxn];
char s[maxn << 1], s1[maxn];
pair<int, int> a[maxn];
void change() {
for (int i = 1, t = 0; i <= n; i++) {
s[++t] = '#';
s[++t] = s1[i];
}
s[0] = '$', s[2 * n + 1] = '#', s[2 * n + 2] = '@', s[2 * n + 3] = ' ';
n = 2 * n + 3;
}
void ma() {
int id = 0;
for (int i = 1; i <= n; i++) {
int t = i > len[id] + id ? t = 0 : min(len[2 * id - i], id + len[id] - i);
while(s[i + t] == s[i - t]) t++;
len[i] = t - 1;
if (id + len[id] <= i + len[i]) {
id = i;
}
}
id = 0;
for (int i = 1; i <= n; i++) if (s[i] != '#') {
len[++id] = len[i] / 2;
}
}
struct query {
int l, r, x;
bool operator < (const query& i) const {
if (x == i.x) return l < i.l;
return x < i.x;
}
} q[maxn];
int lowbit(int x) {
return x & (-x);
}
void add(int i, int x) {
while(i <= m) {
b[i] += x;
i += lowbit(i);
}
}
int sum(int i) {
int res = 0;
while(i > 0) {
res += b[i];
i -= lowbit(i);
}
return res;
}
void solve() {
int t = 0;
for (int i = 1; i <= m; i++) if (len[i]) {
q[t].l = i + 1;
q[t].r = i + len[i];
q[t++].x = len[i] + m - i - len[i] + 1;
}
for (int i = 1; i <= m; i++) {
a[i].first = len[i] + m - i + 1;
a[i].second = i;
}
sort(a + 1, a + m + 1);
sort(q, q + t);
int j = 1;
long long res = 0;
for (int i = 0; i < t; i++) {
while(a[j].first < q[i].x && j <= m) {
add(a[j].second, 1);
j++;
}
res += (q[i].r - q[i].l + 1) - (sum(q[i].r) - sum(q[i].l - 1));
}
printf("%lld
", res);
}
int main()
{
int cas;
cin >> cas;
while (cas--)
{
/* code */
scanf("%s", s1 + 1);
memset(len, 0, sizeof(len));
memset(b, 0, sizeof(b));
m = n = strlen(s1 + 1);
change();
ma();
solve();
}
return 0;
}
1346E - Magic Tricks
(n)个位置,每个位置上都有一个球。初始时,第(k)个位置上是一个特殊的球。现给出(m)次交换,记(t_i)表示(m)次交换后,特殊的球被交换到了(i)位置的最少 (fake ~ swap) 次数。一次交换被称为(fake ~ swap) 当且仅当这次交换什么都没做,既不发生交换。求出所有(t_i)((1 leq i leq n))。
(1 leq n,m leq 2e5)
题解
时间是线性的,令(dp[a][b]:=) (b)次交换后,特殊的球被交换到(a)位置的最少(fake ~ swap)次数,有:
1345D - Monopole Magnets
给一个(n*m)的棋盘,每个格子都有一种颜色,黑色或者白色,然后将一些(N)和(S)放入棋盘的格子中,一个格子可以放入多个,且(N)和(S)可以放在同一个格子里面。求最少需要放入(N)的个数((S)的个数不限)使得满足如下三个条件:
- 棋盘的每行每列至少有一个(S)。
- 对于黑色位置((i,j)),经过有限次操作后,至少存在一个(N)能到达这些位置。
- 对于白色位置((i,j)),无论经过多少次的操作,都没有(N)能到达这些位置。
一次操作:选取任意两个在同行或者同列,但不在同一个格子里面的(N)和(S),保持(S)的位置不变,(N)向(S)靠近一步。
(1 leq n,m leq 1000)
题解
先判断不存在解的情况。如果((i,j))位置是白色,且这个位置放(S),那么第(i)行和第(j)列不能存在黑色位置,因为既然能达到黑色位置,则经过有限次操作后,必定能到达((i,j)),而((i,j))是不可达的。如果((i,j))位置是黑色,且这个位置放(S),那么第(i)行和第(j)列只能存在一段连续的黑色位置,因为若存在两端或者更多段黑色位置,则段与段之间白色的部分必定可达。检查完所有的位置后,如果某行或者某列所有的位置都不能放(S),则解不存在。
经过上面的检查后,棋盘中黑色的位置组成了一个个连通块,在每个连通块的边界都放上(S),显然,一个连通块只需要在块内任意位置处放入一个(N)就行了。故最少的(N)的个数就是连通块的个数。
1343D - Constant Palindrome Sum
给一个长度为(n)的序列(A),问最少需要几次操作使得(forall i in [1;frac{n}{2}]),有(a_i + a_{n-i+1} = X)。
一次操作:将任意一个数置为区间([1;k])内的数。
(1 leq a_i leq k leq 2e5),(1 leq n leq 2e5),且(n)为偶数。
题解
记(x = min(a_i, a_{n - i + 1})),(y = max(a_i, a_{n - i + 1}))。如果(X)落在区间:
- ([2, x]),第(i)对的贡献为2
- ([x + 1, x + y - 1]),第(i)对的贡献为1
- ([x+y, x+y]),第(i)对的贡献为0
- ([x + y + 1, k + y]),第(i)对的贡献为1
- ([k + y + 1, 2 * k]),第(i)对的贡献为2
显然,我们可以在每一对 (i) 所对应的这些区间上预先加上贡献,最后求区间([2; 2*k])上的最小值就是最少操作次数。区间操作,单点查询,可以用树状数组。但还有比较简单的方法,利用差分:在区间([l,r])上加1,则(b[l] += 1),(b[r + 1] -= 1),处理完所有的区间后,扫描一遍求前缀和,既(b[i] += b[i - 1]),最后(answer = min{b_i})。
1342C - Yet Another Counting Problem
给两个正整数(a,b),问在区间([L,R])内有多少个数满足:((x \% a) \% b ~~!= ~~(x\%b)\%a) ?
(1 leq a, b, L, R leq 1e18)
题解
不妨假设(a leq b),如果((x \% a) \% b ~~= ~~(x\%b)\%a),有:(x \% a ~~= ~~(x\%b)\%a),并且(a | (x - x \% b))。假设(x = p * b + r),则有(a |(p*b)),既(p)是(frac{a}{gcd(a,b)})的倍数,既(p)的取值为:({0, frac{a}{gcd(a,b)}, frac{2a}{gcd(a,b)}, frac{3a}{gcd(a,b)},... }),而(r)的取值为:({0, 1, 2, 3, ...,b-1})。所以区间([0, R])内满足相等条件的(x)的个数为:(frac{(R+1)*gcd(a,b)}{ab} * b + min(b, (R+1)\%(frac{ab}{gcd(a,b)})))。
1342D - Multiple Testcases
给一个长度为(n)的序列(A),(1 leq a_i leq k),和长度为(k)的序列(C),(c_1 geq c_2 geq c_3 geq ... geq c_k),问(A)最少能被分成几组,使得每组都满足如下条件:
- 记每组中大于或者等于(i) 的个数为(g_i),有:(g_i leq c_i)。
(1 leq n,k le 2e5)
题解
记最少组数为(ans),有:(ans = maxlceil frac{g_i}{c_i} ceil)。怎么构造呢,令(a_i in ans[i \% ans]),(i in {0,1,2,...,n-1})。还是比较巧妙得!
1341D - Nastya and Scoreboard
给(n)个数字显示器,问再打开(k)个显示器中导管后,这(n)个数字显示器所组成的最大整数?
(1 leq n, k leq 2000)
题解
令(dp[i][j]:=) (i) 个数字显示器再打开(j)个导管时,第(i)个数字显示器显示的最大数字。为了满足最大的整数,第一个显示器显示的数字应该尽可能的大,实际上就是(dp[0][k])。从后向前递归,**记录所有合法的状态(dp[i][j]) **,再从前向后回溯就能找到所有合法的显示器的数字了。
string s[10] = {"1110111", "0010010", "1011101", "1011011", "0111010", "1101011", "1101111", "1010010", "1111111", "1111011"};
int get_int(string str) {
int res = 0;
for (int i = 6, j = 1; ~i; i--, j *= 2) if (str[i] == '1') res += j;
return res;
}
int main()
{
memset(dp, -1, sizeof(dp));
memset(mp, -1, sizeof(mp));
cin >> n >> k;
for (int i = 0; i < n; i++) {
string t;
cin >> t;
int x = get_int(t);
for (int j = 0; j < 10; j++) {
int y = get_int(s[j]);
if ((x & y) == x) mp[i][__builtin_popcount(x ^ y)] = j;
}
}
for (int i = 0; i < 8; i++) dp[n - 1][i] = mp[n - 1][i];
for (int i = n - 2; ~i; i--) {
for (int j = 0; j <= k; j++) if (dp[i + 1][j] >= 0){
for (int p = 0; p <= 7 && j + p <= k; p++) dp[i][j + p] = max(dp[i][j + p], mp[i][p]);
}
}
int cnt = 0;
for (int i = 0; i < n; i++) {
int u = dp[i][k - cnt];
cout << u;
if (u == -1) return 0;
for (int j = 0; j <= 7; j++) if (mp[i][j] == u) {
cnt += j;
break;
}
}
cout << endl;
return 0;
}
1338B - Edge Weight Assignment(好题:通过构造点权值来构造边权值)
给一颗树,为每条边赋一个权值(正整数),问最少和最多需要几个不同的正整数,使得任意一对叶子之间路径异或值为0,既有:(l_1w_0v_1w_1v_2w_2...v_iw_il_2),(w_0 igoplus w_1 igoplus w_2 igoplus ... igoplus w_i = 0)。
题解
最少需要1个或者3个,也就是对所有的边赋值同一个整整数,分类讨论一下即可。
为每个叶子节点赋点权值:0,为非叶节点赋点权值:(2^i)。则边权值为:(w(e(i,j)) = v_i igoplus v_j)。显然,任意一对叶子之间的路径异或为0,所以最多需要(e - sum_v(leaf(v) - 1))个正整数。简直妙级了!
(leaf(v))表示节点(v)的叶儿子个数.
1336A - Linova and Kingdom
给一颗(n)个节点的树,根为1,从树中选取(k)个节点建立工厂,剩余的所有节点建立公园,问这(k)个工厂各自派出一个工人,这些工人走到树根最多能游玩几个公园?工人只能沿着最短路走。
(1 leq k leq n leq 2e5)
题解
当一个节点被选择建立工厂时,它的所有子节点在之前就已经被选择建立工厂,否则具有更优的方案。所以每个节点的实际贡献为(dep[u] - size[u])。求出每个点的实际贡献,排个序,从大往小选择(k)个贡献较大的节点。
1333C - Eugene and an array
给一个序列,问有多少个子串满足:子串的任意一个子串都是好子串 ?
- 好子串(A_{l...r}):(a_l + a_{l +1} + a_{l + 2} + ... + a[r] eq 0)
(1 leq n leq 2e5),(-10^9 leq a_i leq 10^9)
题解
令(R[i])表示以(i)为左端点且最短 非好子串 的右端点位置,既(a_i + a_{i + 1} + a_{i + 2} + ... + a_{R[i]} = 0)。当然,也可能不存在(R[i])。记(L[R[i]] = i),那么(i)位置为左端点的好子串个数就是:(R[j] - i) 或者是 (n - i + 1),(j = (argmin_jR[j] geq i) && (L[R[j]] geq i))。(j) 的取值应该使得(R[j])尽量靠近(i)的同时其左边界要大于等于(i)。
怎么求(R[i])?求出所有位置的前缀和(sum[i]),显然有(sum[R[i]] = sum[i-1]),用一个二维数组记录相同(sum)值出现的所有位置,然后二分查找满足等式且最小的(R[i])就可以了。