【CodeForces】EDU70重练

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 xy计算器,其中 0 ≤ x , y ≤ 9 0\le x,y\le 9 0x,y9,该计算器只能够输入 x , y x,y x,y

这个计算器的初值为 0 0 0,现在可以把这个数值加上 x x x或者 y y y,在加上输入的数之前,计算器会把当前数值的最后一位数输出。

现在给定一个数字序列,要求对于任意一个 x − y x-y xy计算器经过一系列操作后得到的序列的某个子序列为给定的序列,求最少的操作步骤。

分析

显然我们先暴力枚举 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,DW,S。所以我们分开考虑。由于两种的计算方法是相同的,所以我们只讨论一种。我们以A,D为例。

记命令遇到A,D时的前缀和为 s s s。当碰到A s − 1 s-1 s1,碰到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 17

我们发现,当填上 a a a 3 3 3时,所产生的 1337 1337 1337数量为 a ( a − 1 ) 2 \frac{a(a-1)}{2} 2a(a1)

所以我们先暂时填上 a a a 3 3 3。那么对于剩下的 N − a ( a − 1 ) 2 N-\frac{a(a-1)}{2} N2a(a1) 1337 1337 1337,我们在第 2 2 2 3 3 3之后填上这么多个 7 7 7就可以了。

由于 N ≤ 1 0 9 N\le 10^9 N109,我们所需的 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=1Nj=1NF(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(Lsit+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,pN,那么我们的循环节长度就是 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×paqb×pb

但又因为我们的剩余字符数量不能超过现在用掉的数量(不然我们又可以做一个循环节出来),那么我们又可以得到: { 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+1aqapap+1bqbpb

然后发现这东西可以数论分块做。于是分块后,总时间复杂度为 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值