Educational Codeforces Round 70

Contest Info


[Practice Link](https://codeforc.es/contest/1202)
Solved A B C D E F
5/6 Ø Ø Ø Ø Ø -
  • O 在比赛中通过
  • Ø 赛后通过
  • ! 尝试了但是失败了
  • - 没有尝试

Solutions


A. You Are Given Two Binary Strings...

题意:
给出两个数字\(x, y\),令\(f(x)\)\(x\)的二进制表示,现在要选择一个\(k\)使得\(s_k = f(x) + f(y) \cdot 2^k\)的字典序最小。

思路:
考虑乘上\(2^k\)相当于让\(f(y)\)左移\(k\)位,那么我们肯定要让\(f(y)\)的最后一位\(1\)移到离\(f(x)\)最近的一个\(1\)和它进位之后,这样翻转之后字典序是最小的。

view code

#include <bits/stdc++.h>
using namespace std;

define N 100010

char s[N], t[N];

int main() {
int T; scanf("%d", &T);
while (T--) {
scanf("%s%s", s + 1, t + 1);
int lens = strlen(s + 1);
int lent = strlen(t + 1);
int post = 0;
for (int i = lent; i >= 1; --i) {
if (t[i] == '1') {
post = lent - i;
break;
}
}
int res = 0;
for (int i = lens - post; i >= 1; --i) {
if (s[i] == '1') {
res = lens - post - i;
break;
}
}
printf("%d\n", res);
}
return 0;
}

B. You Are Given a Decimal String...

题意:
\(x-y\)计数器,它是这样工作的:

  • 初始时数值为\(0\)
  • 给数值加上\(x\)或者\(y\),然后输出数值模\(10\)后的结果
  • 重复第二步操作,直到输出自己满意的序列
    现在给定一段序列,问对于\(x \in [0, 9], y \in [0, 9]\)\(100\)种计数器,最少需要增加多少数才能使得输出的序列是x-y$计数器合法输出来的?

思路:
考虑对于序列中间隔的两个数\(s[i]\)\(s[i + 1]\),我们定义它们之间的差值为\((s[i + 1] - s[i] + 10) \% 10\),因为是数值的最后一位,所以它们的真实差距肯定可以写成\(10k + (s[i + 1] - s[i])\),但是我们并不需要关心\(k\)是多少。
我们只需要知道需要多少个\((px + qy) \% 10 = (s[i + 1] - s[i] + 10) \% 10\)
这个暴力枚举一下\(p, q\)即可,而显然\(p \in [0, 9], q \in [0, 9]\),因为存在模\(10\)操作,\((px + qy) \% 10 = (p \% 10 x + q \% 10 ) \% 10\)

view code

#include <bits/stdc++.h>
using namespace std;

const int INF = 0x3f3f3f3f;
const int N = 2e6 + 10;
char s[N];
int n, b[20];
void Min(int &x, int y) {
if (x > y) x = y;
}

int main() {
while (scanf("%s", s + 1) != EOF) {
n = strlen(s + 1);
for (int x = 0; x < 10; ++x) {
for (int y = 0; y < 10; ++y) {
for (int k = 0; k < 10; ++k) b[k] = INF;
for (int p = 0; p < 10; ++p) {
for (int q = 0; q < 10; ++q) if (p | q) {
Min(b[(p * x + q * y) % 10], p + q - 1);
}
}
int ans = 0;
for (int i = 1; i < n; ++i) {
int t = (s[i + 1] - s[i] + 10) % 10;
if (b[t] == INF) {
ans = -1;
break;
}
ans += b[t];
}
printf("%d%c", ans, " \n"[y == 9]);
}
}
}
return 0;
}

C. You Are Given a WASD-string...

题意:
有一个机器人,现在给出一系列行走步骤,问能否在任意处添加一个步骤,使得它的行动范围尽量小。
行动范围的定义为用一个最小的矩形框住它的行动路径。范围即为矩形的面积。

思路:
将横竖分开考虑,如果可以缩小行动范围,那么必然是长缩短一或者宽缩短一。
我们考虑什么时候边长可以缩短一:

  • 考虑从一个位置插入一个状态,那么考虑从从这个点之后的所有这个方向的路径都会整体往上移动一个或者往下移动一个
  • 那么判断一下这个移动是否有效即可。
  • 有效的定义为这个点之后的路径的最值比这个点以及之前的最值大,这样它的最值才会减一,不然的话这个点以及之前的最值仍然掌控着矩形边界
view code

#include <bits/stdc++.h>
using namespace std;

define ll long long

define N 200010

char s[N];

int main() {
int T; scanf("%d", &T);
while (T--) {
scanf("%s", s + 1);
int n = strlen(s + 1);
int nowx = 0, nowy = 0;
int up = 0, down = 0, left = 0, right = 0;
int gup = 0, gdown = 0, gleft = 0, gright = 0;
for (int i = 1; i <= n; ++i) {
if (s[i] == 'W') --nowx;
if (s[i] == 'S') ++nowx;
if (s[i] == 'A') --nowy;
if (s[i] == 'D') ++nowy;
up = min(up, nowx);
down = max(down, nowx);
left = min(left, nowy);
right = max(right, nowy);
gup = max(gup, nowx - up);
gdown = max(gdown, down - nowx);
gleft = max(gleft, nowy - left);
gright = max(gright, right - nowy);
}
ll x[2], y[2];
x[0] = max(gup, gdown);
x[1] = max(1ll * (gup || gdown), x[0] - (gup != gdown));
y[0] = max(gleft, gright);
y[1] = max(1ll * (gleft || gright), y[0] - (gleft != gright));
printf("%lld\n", min((x[0] + 1) * (y[1] + 1), (x[1] + 1) * (y[0] + 1)));
}
return 0;
}

D. Print a 1337-string...

题意:
构造一个序列,这个序列只包含\(\{1, 3, 7\}\),并且一共有\(n\)的子序列是\(1337\)

思路:
考虑最后一位放\(7\)
然后放\(x\)\(3\)
那么每放一个\(1\)可以产生\([1, \frac{x(x - 1)}{2}]\)\(1337\)
那么考虑从大到小贪心放即可,总能放完。

view code

#include <bits/stdc++.h>
using namespace std;

define ll long long

define N 100010

ll n;
ll f(ll x) {
return x * (x - 1) / 2;
}

int main() {
int T; scanf("%d", &T);
while (T--) {
scanf("%lld", &n);
for (int i = 32000; i >= 2; --i) {
while (f(i) <= n) {
n -= f(i);
putchar('1');
}
putchar('3');
}
assert(n == 0);
puts("37");
}
return 0;
}

E. You Are Given Some Strings...

题意:
给出一个文本串\(T\),和若干个模式串\(S_i\),定义\(f(t, s)\)\(s\)\(t\)中出现的次数。
计算下式:

\[\begin{eqnarray*} \sum\limits_{i = 1}^n \sum\limits_{j = 1}^n f(t, s_i + s_j) \end{eqnarray*} \]

思路:
考虑\(T\)对整体的贡献,即使\(T\)中存在多少个子串,使得可以从某个中间位置切开,使得左右两边都是模式串。
那么枚举这个中间位置即可,做两边\(AC\)自动机,求出\(f[i], g[i]\),分别表示以\(i\)结尾的模式串匹配次数和以\(i\)开头的模式串匹配次数。
那么乘一乘即是结果。

view code

#include <bits/stdc++.h>
using namespace std;

define ll long long

define N 200010

int n;
string s[N], t, tr;
ll f[N], g[N];

define ALP 26

struct ACAM {
struct node {
int nx[ALP], fail;
int cnt;
node() {
memset(nx, -1, sizeof nx);
cnt = 0;
}
}t[N];
int root, tot;
int que[N], ql, qr;
//节点从1开始
int newnode() {
++tot;
t[tot] = node();
return tot;
}
void init() {
tot = 0;
root = newnode();
}
void insert(string s) {
int len = s.size();
int now = root;
for (int i = 0; i < len; ++i) {
if (t[now].nx[s[i] - 'a'] == -1)
t[now].nx[s[i] - 'a'] = newnode();
now = t[now].nx[s[i] - 'a'];
}
++t[now].cnt;
}
void build() {
ql = 1, qr = 0;
t[root].fail = root;
for (int i = 0; i < ALP; ++i) {
if (t[root].nx[i] == -1) {
t[root].nx[i] = root;
} else {
t[t[root].nx[i]].fail = root;
que[++qr] = t[root].nx[i];
}
}
while (ql <= qr) {
int now = que[ql++];
t[now].cnt += t[t[now].fail].cnt;
for (int i = 0; i < ALP; ++i) {
if (t[now].nx[i] == -1) {
t[now].nx[i] = t[t[now].fail].nx[i];
}
else {
t[t[now].nx[i]].fail = t[t[now].fail].nx[i];
que[++qr] = t[now].nx[i];
}
}
}
}
void query(string s, ll *f) {
int len = s.size();
int now = root;
for (int i = 0; i < len; ++i) {
now = t[now].nx[s[i] - 'a'];
f[i] = t[now].cnt;
}
}
}ac;

int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
while (cin >> t) {
tr = t; reverse(tr.begin(), tr.end());
cin >> n;
ac.init();
for (int i = 1; i <= n; ++i) cin >> s[i], ac.insert(s[i]);
ac.build();
ac.query(t, f);
ac.init();
for (int i = 1; i <= n; ++i) {
reverse(s[i].begin(), s[i].end());
ac.insert(s[i]);
}
ac.build();
ac.query(tr, g);
ll res = 0;
int len = t.size();
reverse(g, g + len);
for (int i = 0; i < len - 1; ++i) {
res += f[i] * g[i + 1];
}
cout << res << "\n";
}
return 0;
}

posted @ 2019-08-09 21:58  Dup4  阅读(245)  评论(0编辑  收藏  举报