D. Linguistics(贪心/思维)

题目
题意:给定一个只包含A B字符的字符串,问该字符串能否刚好由a个’A’, b个’B’, c个’AB’, d个‘BA’拼凑而成。

思路:容易想到的是bfs暴搜,嗯,,T了。

#include <bits/stdc++.h> 
using namespace std;
const int maxn = 200010;

int a, b, c, d, n;
char s[maxn];
struct node {
	int a, b, c, d;
	int pos;
	node(int _a, int _b, int _c, int _d, int _p) {
		a = _a;
		b = _b;
		c = _c;
		d = _d;
		pos = _p;
	}
	bool operator < (const node & h) const {
		if (a == h.a) {
			if (b == h.b) {
				if (c == h.c) {
					if (d == h.d) {
						return pos < h.pos; 
					}
					return d < h.d;
				}
				return c < h.c;
			}
			return b < h.b;
		}
		return a < h.a;
	}
	void print() {
		printf("%d %d %d %d (%d)\n", a, b, c, d, pos);
	}
};
multiset<node> st;
void solve() {
	scanf("%d%d%d%d", &a, &b, &c, &d);
	scanf("%s", s);
	n = strlen(s);
	bool flag = 0;
	st.clear();
	queue<node> q;
	q.push(node(0, 0, 0, 0, 0));
//	printf("s :%s\n", s);// debug
	while (!q.empty()) {
		node cur = q.front();
		q.pop();
		if (st.find(cur) != st.end()) {
			continue;
		}
		st.insert(cur);
//		cur.print();// debug
		if (cur.a == a && cur.b == b &&
			cur.c == c && cur.d == d &&
			cur.pos == n) {
			flag = 1;
			break;
		}
		if (cur.pos == n) {
			continue;
		}
		if (s[cur.pos] == 'A' && cur.a < a) {
			node tmp = cur;
			++tmp.pos;
			++tmp.a;
			q.push(tmp);
		}
		if (s[cur.pos] == 'B' && cur.b < b) {
			node tmp = cur;
			++tmp.pos;
			++tmp.b;
			q.push(tmp);
		}
		if (cur.pos + 1 < n && cur.c < c &&
			s[cur.pos] == 'A' && s[cur.pos+1] == 'B') {
			node tmp = cur;
			tmp.pos += 2;
			++tmp.c;
			q.push(tmp);	
		}
		if (cur.pos + 1 < n && cur.d < d &&
			s[cur.pos] == 'B' && s[cur.pos+1] == 'A') {
			node tmp = cur;
			tmp.pos += 2;
			++tmp.d;
			q.push(tmp);	
		}
	}
	printf("%s\n", flag ? "Yes" : "No");
}
int main() {
	int t;
	scanf("%d", &t);
//	t = 1;
	while (t--) {
		solve();
	}
}
/*

*/
  • 首先,可以关注字符数是否一致,即A 和 B 的数量是否一致。
  • 其次,只需关注能否匹配 c个’AB’,d个’BA’。我们把愿字符串拆分出来,对于出现相邻相等的部分,必然不可以用来拼接AB和BA,因此,我们可以把原字符串拆分成若干个相邻位置不相同的子字符串,即"ABA…“或"BAB…”。

子字符串的分配,也有讲究。

  1. 对于子字符串,如果它长度len为奇数,那么它可以组成x个AB,y个BA,只要x+y==len/2。如ABABA,它可以组成2 AB , 2BA,或者AB + BA。
  2. 如果它长度为偶数,且以AB开头,那么我们可以优先分配给AB类型的。因为这种场景下,不浪费字符,如果先分配给BA类型,则会引起字符浪费。如ABAB,它可以分配2AB,却只能分配1 BA。
  3. 如果它长度为偶数,且以BA开头,优先分配给BA类型的。原因同上。

按照这种思路去分配就够了吗,并不。我们还需要遵循短字符串优先分配的原则。就是相同类型的字符串,我们要优先消费短的。为啥?
a,b,c,d取 1 1 2 3
字符串取 ABABABBAABAB

按照上诉思路,我们拆分字符串为ABABAB, BA, ABAB这3种子串。

如果先消费ABABAB,由于优先分配给AB,ABABAB剩下AB,再分配给BA,此时贡献0个BA。后边的BA,ABAB总共贡献BA个数为2,不能满足要求。

而如果先消费ABAB, 由于优先分配给AB,ABAB刚好分配2个AB。后边ABABAB, BA再去分配BA,就有3个了,可以满足要求。

我们发现,优先消费短字符串,可以让更长的字符串给另一种类型做消费

因此,第4个消费逻辑,优先消费短的子字符串。

#include <bits/stdc++.h> 
using namespace std;
const int maxn = 200010;

int a, b, c, d, n;
char s[maxn];
// 判断 A B字符数是否一致 
bool check() {
	int numa = 0;
	for (int i = 0; i < n; ++i) {
		if (s[i] == 'A') {
			++numa;
		}
	}
	return numa == a + c + d;
}

// 消费字符串,优先给fir,其次给sec 
void cal(int &fir, int x, int &sec) {
	if (fir >= x) {
		fir -= x;
	} else {
		x -= fir + 1; 
		fir = 0;
		sec -= min(sec, x);
	}
}
void solve() {
	scanf("%d%d%d%d", &a, &b, &c, &d);
	scanf("%s", s);
	n = strlen(s);
	if (!check()) {
		printf("No\n");
		return;
	}
	int co = 0;
	int i = 0;
	vector<int> vc, vd;
	while (i < n) {
		int j = i + 1;
		while (j < n && s[j] != s[j-1]) ++j;
		int len = j - i;// 子字符串长度 
		if (len == 1) {
			i = j;
			continue;
		}
		if (len & 1) {// 奇数字段,可任意取AB或BA, 提取到公共计数 
			co += len / 2;
		} else {// 偶数字段 
			len /= 2;
			if (s[i] == 'A') {
				vc.push_back(len);
//				cal(c, len, d);
			} else {
				vd.push_back(len);
//				cal(d, len, c);
			}
		}
		i = j;
	}
	// 从小到大排序,优先消费短的 
	sort(vc.begin(), vc.end());
	sort(vd.begin(), vd.end());
	for (auto len: vc) {
		cal(c, len, d);
	}
	for (auto len: vd) {
		cal(d, len, c);
	}
//	printf("(%d, %d, %d)\n", co, c, d);
	printf("%s\n", co >= c + d ? "Yes" : "No");
}
int main() {
	int t;
	scanf("%d", &t);
//	t = 1;
	while (t--) {
		solve();
	}
}
/*

*/
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值