Educational Codeforces Round 70 (Rated for Div. 2) 题解
怎么全和字符串有关???
A. You Are Given Two Binary Strings…
题目大意
给定两个二进制串 x , y x,y x,y,现可以将 y y y左移 k k k位与 x x x相加并将得到的串翻转。问使得翻转后的串最小的 k k k。
分析
由于我们要反过来,所以我们需要尽量使得得到的相加后的串的末尾的 0 0 0尽可能多。
我们就可以尽力将 y y y的最后一个 1 1 1通过二进制加法把它变成 0 0 0。
我们记从右往左数的在 y y y中的第一个 1 1 1的位置为 p ( y ) p(y) p(y)。再在 x x x中进行相同的操作得到 p ( x ) p(x) p(x),注意 p ( x ) ≥ p ( y ) p(x)\ge p(y) p(x)≥p(y)。则 p ( x ) − p ( y ) p(x)-p(y) p(x)−p(y)就是我们的答案。
参考代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int Maxn = 1e5;
char s1[Maxn + 5], s2[Maxn + 5];
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
int T;
scanf("%d", &T);
while(T--) {
scanf("%s %s", s1 + 1, s2 + 1);
int len1 = strlen(s1 + 1), len2 = strlen(s2 + 1);
int cnt = 0, ans = 0;
for(int i = len2; i >= 1; i--)
if(s2[i] == '0') cnt++;
else break;
for(int i = len1 - cnt; i >= 1; i--)
if(s1[i] == '0') ans++;
else break;
printf("%d\n", ans);
}
return 0;
}
B. You Are Given a Decimal String…
题目大意
有一种特殊的计算器叫 x − y x-y x−y计算器,其中 0 ≤ x , y ≤ 9 0\le x,y\le 9 0≤x,y≤9,该计算器只能够输入 x , y x,y x,y。
这个计算器的初值为 0 0 0,现在可以把这个数值加上 x x x或者 y y y,在加上输入的数之前,计算器会把当前数值的最后一位数输出。
现在给定一个数字序列,要求对于任意一个 x − y x-y x−y计算器经过一系列操作后得到的序列的某个子序列为给定的序列,求最少的操作步骤。
分析
显然我们先暴力枚举 x , y x,y x,y。
对于数 i , j i,j i,j,我们设通过一系列操作使得 i i i加到 j j j的最小操作步数为 d ( i , j ) d(i,j) d(i,j)。
如何求解 d ( i , j ) d(i,j) d(i,j)?我们考虑通过最短路来求。
将 i i i向 ( i + x ) m o d 10 (i+x)\mod 10 (i+x)mod10和 ( i + y ) m o d 10 (i+y)\mod 10 (i+y)mod10各连一条长度为 1 1 1的边,跑一遍Floyd就可以求出 d ( i , j ) d(i,j) d(i,j)。
最后扫一遍字符串就可以统计出答案了。注意统计时 d ( i , j ) d(i,j) d(i,j)要减去1。
参考代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int Maxn = 2e6;
const int INF = 0x3f3f3f3f;
char s[Maxn + 5];
int dis[13][13];
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
scanf("%s", s);
int len = strlen(s);
for(int x = 0; x <= 9; x++) {
for(int y = 0; y <= 9; y++) {
memset(dis, 0x3f, sizeof dis);
for(int i = 0; i <= 9; i++)
dis[i][(i + x) % 10] = dis[i][(i + y) % 10] = 1;
for(int k = 0; k <= 9; k++)
for(int i = 0; i <= 9; i++)
for(int j = 0; j <= 9; j++)
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
ll ans = 0;
bool flag = true;
for(int i = 1; i < len; i++) {
int u = s[i - 1] - '0', v = s[i] - '0';
if(dis[u][v] == INF) {
flag = false;
break;
}
ans += dis[u][v] - 1;
}
printf("%lld ", flag ? ans : -1);
}
puts("");
}
return 0;
}
C. You Are Given a WASD-string…
题目大意
给定一个只含有W,S,A,D
的字符串,每个字符为一个命令,可以操控一个机器人向上、向下、向左、向右走一格。那么这个机器人会走出一个轨迹,我们用最小的矩形框住它。
现在可以向里面的任意位置添加四个字符中的一个,求添加后这个矩形的面积的最小值。
分析
由于矩形的宽度和高度分别取决于A,D
和W,S
。所以我们分开考虑。由于两种的计算方法是相同的,所以我们只讨论一种。我们以A,D
为例。
记命令遇到A,D
时的前缀和为
s
s
s。当碰到A
时
s
−
1
s-1
s−1,碰到D
时
s
+
1
s+1
s+1,则它的宽度就是
max
{
s
}
−
min
{
s
}
+
1
\max\{s\}-\min\{s\}+1
max{s}−min{s}+1
现在考虑插入一个D
,这意味着我们后面的所有的
s
s
s都得加上
1
1
1。
比较暴力的做法是直接用线段树维护。
我们进一步思考。
要使矩形的宽度变小,意味着我们必须在加入这个D
后,最小值变大,还要保证最大值不变。
我们简单手玩一下可以发现,这个D
必须插在最后一个最大值之后,第一个最小值之前,才能使宽度变小。
对于其他字符,我们用相同的操作可以得到。
注意当宽度和长度都可以缩时,应特判到底缩哪边使答案最小。
注意当某个方向上的操作序列长度小于 2 2 2时,这个方向是无法缩的。
参考代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int Maxn = 2e5;
char s[Maxn + 5];
int len, flag;
int solve(char c1, char c2) {
int mx = 0, mn = 0, sum = 0, cnt = 0;
int last_mx = -1, last_mn = -1, first_mx = -1, first_mn = -1;
for(int i = 0; i < len; i++) {
bool ok = false;
if(s[i] == c1) sum--, ok = true;
if(s[i] == c2) sum++, ok = true;
if(!ok) continue;
if(sum == mx) last_mx = i;
if(sum > mx) last_mx = first_mx = i, mx = sum;
if(sum == mn) last_mn = i;
if(sum < mn) last_mn = first_mn = i, mn = sum;
cnt++;
}
int ret = mx - mn + 1;
if(cnt > 1 && (last_mx < first_mn || first_mx > last_mn))
flag++, ret--;
return ret;
}
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
int _;
scanf("%d", &_);
while(_--) {
scanf("%s", s);
len = strlen(s);
int W = solve('W', 'S');
int H = solve('D', 'A');
ll ans = (flag == 2 ? min(1LL * (H + 1) * W, 1LL * H * (W + 1)) : 1LL * W * H);
printf("%lld\n", ans);
flag = 0;
}
return 0;
}
D.Print a 1337-string…
题目大意
给定一个数 N N N,要求构造一个只含有 1 , 3 , 7 1,3,7 1,3,7的字符串,使得它的子串 1337 1337 1337的数量恰好为 N N N,串长度不超过 1 0 5 10^5 105。
分析
考虑我们构造一个这样形式的串: 1 … 7 1\ldots7 1…7。
我们发现,当填上 a a a个 3 3 3时,所产生的 1337 1337 1337数量为 a ( a − 1 ) 2 \frac{a(a-1)}{2} 2a(a−1)。
所以我们先暂时填上 a a a个 3 3 3。那么对于剩下的 N − a ( a − 1 ) 2 N-\frac{a(a-1)}{2} N−2a(a−1)个 1337 1337 1337,我们在第 2 2 2个 3 3 3之后填上这么多个 7 7 7就可以了。
由于 N ≤ 1 0 9 N\le 10^9 N≤109,我们所需的 3 3 3的数量不会超过 4500 4500 4500,故可过。
参考代码
#include <cstdio>
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
int _;
scanf("%d", &_);
while(_--) {
int N, len3 = 2;
string ans;
scanf("%d", &N);
while(len3 * (len3 + 1) / 2 <= N)
len3++;
N -= (len3 * (len3 - 1) / 2);
ans = "133";
while(N--) ans += "7";
len3 -= 2;
while(len3--) ans += "3";
ans += "7";
cout << ans << endl;
}
return 0;
}
E.You Are Given Some Strings…
题目大意
给定一个字符串 t t t和 N N N个字符串 s i s_i si,定义函数 F ( t , s i ) F(t,s_i) F(t,si)为 s i s_i si在 t t t中出现的次数。
现在要求求出 ∑ i = 1 N ∑ j = 1 N F ( t , s i + s j ) \sum_{i=1}^{N}\sum_{j=1}^{N}F(t, s_i+s_j) i=1∑Nj=1∑NF(t,si+sj)
其中 s i + s j s_i+s_j si+sj是表示将 s i , s j s_i,s_j si,sj两个串连在一起。
注意若有两对不同位置的串连成的串是一样的,你需要将答案计算两次。
分析
注意到当两个串 s i , s j s_i,s_j si,sj连在一起的时候,当 s i s_i si结束时, s j s_j sj也就开始了。
所以我们在 T T T串上统计有多少个位置是 s i s_i si结束 s j s_j sj开始的地方。
所以你用了AC自动机,后缀数组,后缀自动机等一系列毒瘤算法
我们考虑将串分成两类:一类是长度小于一个定值 L L L,另一类是长度大于 L L L的。
对于长度大于 L L L的串,我们将 s i s_i si和 t t t连到一起成为一个大的字符串,计算它的 Z Z Z函数。对于 Z ( j + l e n ( s i ) ) ≥ l e n ( s i ) Z(j+len(s_i))\ge len(s_i) Z(j+len(si))≥len(si)的位置,它对 j j j位置的贡献加一。
对于长度小于等于
L
L
L的串,我们把它们挂到一棵Trie
上去,最后对于
T
T
T的每个位置,暴力查找即可。
总时间复杂度为 O ( ∑ ∣ s i ∣ L ⋅ ∣ t ∣ + ∑ ∣ s i ∣ ) O(\frac{\sum |s_i|}{L}\cdot|t|+\sum{|s_i|}) O(L∑∣si∣⋅∣t∣+∑∣si∣)。
可以证明当 L = ∑ s i L=\sqrt{\sum s_i} L=∑si时,总时间复杂度最小。
还有注意调用函数时加上引用,不然就会T掉。加了就跑得飞快。。。
补充: Z Z Z函数
这部分参考自:https://www.cnblogs.com/SuuT/p/11385349.html
对于一个长度为 N N N的串,我们定义 Z ( i ) Z(i) Z(i)为从第 i i i个位置开始的子串和原串的最长公共前缀的长度。注意 Z ( 0 ) Z(0) Z(0)没有定义。
- “aaaaa” : [ 0 , 4 , 3 , 2 , 1 ] [0,4,3,2,1] [0,4,3,2,1];
- “aaabaab” - [ 0 , 2 , 1 , 0 , 2 , 1 , 0 ] [0,2,1,0,2,1,0] [0,2,1,0,2,1,0]。
我们可以用
O
(
N
)
O(N)
O(N)的时间计算出一个串所有的
Z
Z
Z函数。具体就参考我的代码和上面给的博客链接吧 (大量英文警告。其实是我懒不想翻译了)
参考代码
#include <cmath>
#include <cstdio>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int Maxn = 2e5;
const int LengthLimit = 500;
struct TrieNode {
TrieNode *son[27];
int cnt;
};
TrieNode pool[Maxn * 2 + 5];
TrieNode *root, *ncnt;
TrieNode *newnode() {
TrieNode *p = ++ncnt;
for(int i = 0; i < 27; i++)
p->son[i] = NULL;
p->cnt = 0;
return p;
}
void init() {
ncnt = &pool[0];
root = newnode();
}
void add(const string &str) {
TrieNode *p = root;
for(int i = 0; i < (int)str.size(); i++) {
int ch = str[i] - 'a';
if(!p->son[ch]) p->son[ch] = newnode();
p = p->son[ch];
}
p->cnt++;
}
int count(const string &str, int pos) {
int ret = 0;
TrieNode *p = root;
while(pos < (int)str.size()) {
int ch = str[pos] - 'a';
if(!p->son[ch]) break;
p = p->son[ch];
ret += p->cnt, pos++;
}
return ret;
}
vector<int> Zfunction(string str) {
vector<int> z(str.size(), 0);
for(int i = 1, l = 0, r = 0; i < (int)str.size(); i++) {
if(i < r) z[i] = min(r - i, z[i - l]);
while(i + z[i] < (int)str.size() && str[i + z[i]] == str[z[i]])
++z[i];
if(i + z[i] > r)
l = i, r = i + z[i];
}
return z;
}
int N;
string T, S[Maxn + 5];
vector<int> cnt[2];
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> T;
cin >> N;
for(int i = 1; i <= N; i++)
cin >> S[i];
for(int k = 0; k < 2; k++) {
init();
cnt[k].assign((int)T.size() + 1, 0);
for(int i = 1; i <= N; i++) {
if((int)S[i].size() > LengthLimit) {
vector<int> z = Zfunction(S[i] + T);
for(int j = 0; j < (int)T.size(); j++)
cnt[k][j] += (z[S[i].size() + j] >= (int)S[i].size());
} else add(S[i]);
}
for(int i = 0; i < (int)T.size(); i++)
cnt[k][i] += count(T, i);
for(int i = 1; i <= N; i++)
reverse(S[i].begin(), S[i].end());
reverse(T.begin(), T.end());
}
ll ans = 0;
for(int i = 0; i < (int)T.size() + 1; i++)
ans += 1LL * cnt[0][i] * cnt[1][T.size() - i];
cout << ans << endl;
return 0;
}
F. You Are Given Some Letters…
题目大意
给定 a a a个 A A A和 b b b个 B B B,要求组成一个长度为 a + b a+b a+b的 A B AB AB串,并求出它们的最小正周期 k k k(即 s i = s i m o d k s_i=s_{i\mod k} si=simodk)。
问有多少种最小正周期 k k k。
分析
我们令 k = ⌊ N p ⌋ , p ∈ N k=\left\lfloor\frac{N}{p}\right\rfloor,p\in \N k=⌊pN⌋,p∈N,那么我们的循环节长度就是 k k k,个数为 p p p。
设 q a , q b q_a,q_b qa,qb分别为一个循环节中 A , B A,B A,B的数量,我们不难得到: { q a + q b = k q a × p ≤ a q b × p ≤ b \begin{cases}q_a+q_b=k\\q_a\times p\le a\\q_b\times p\le b\end{cases} ⎩⎪⎨⎪⎧qa+qb=kqa×p≤aqb×p≤b
但又因为我们的剩余字符数量不能超过现在用掉的数量(不然我们又可以做一个循环节出来),那么我们又可以得到: { q a × ( p + 1 ) ≥ a q b × ( p + 1 ) ≥ b \begin{cases}q_a\times(p+1)\ge a\\q_b\times(p+1)\ge b\end{cases} {qa×(p+1)≥aqb×(p+1)≥b
整理一下就有 { ⌈ a p + 1 ⌉ ≤ q a ≤ ⌊ a p ⌋ ⌈ b p + 1 ⌉ ≤ q b ≤ ⌊ b p ⌋ \begin{cases}\left\lceil\frac{a}{p+1}\right\rceil\le q_a\le\left\lfloor\frac{a}{p}\right\rfloor\\\left\lceil\frac{b}{p+1}\right\rceil\le q_b\le \left\lfloor\frac{b}{p}\right\rfloor\end{cases} ⎩⎨⎧⌈p+1a⌉≤qa≤⌊pa⌋⌈p+1b⌉≤qb≤⌊pb⌋
然后发现这东西可以数论分块做。于是分块后,总时间复杂度为 O ( a + b ) O(\sqrt{a+b}) O(a+b)
参考代码
#include <cstdio>
#include <algorithm>
using namespace std;
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
int a, b;
scanf("%d %d", &a, &b);
int ans = 0;
int N = a + b;
for(int l = 1, k, r; l <= a + b; l = r + 1) {
k = N / l, r = N / k;
int al = (a + k) / (k + 1), ar = a / k;
int bl = (b + k) / (k + 1), br = b / k;
if(ar >= al && br >= bl) ans += max(0, min(r, ar + br) - max(l, al + bl) + 1);
}
printf("%d\n", ans);
return 0;
}