题目大意:
Alina 发现了一种奇怪的语言,它只包含 4 个单词:A、B、AB、BA。事实证明,这种语言中没有空格:一个句子是通过将其单词连接成一个字符串来编写的。
Alina 找到了一个这样的句子 s,她很好奇:它是否可能恰好由 a 词 A、b 词 B、c 词 AB 和 d 词 BA 组成?
换句话说,确定是否可以以某种顺序连接这些 a+b+c+d 单词,以便生成的字符串为 s。每个 a+b+c+d 单词在连接中必须只使用一次,但您可以选择它们连接的顺序。
输入
输入的第一行包含一个整数 t (1≤t≤105) — 测试用例的数量。测试用例的描述如下。
每个测试用例的第一行包含四个整数 a, b, c, d (0≤a,b,c,d≤2⋅105) — 单词 A, B, AB, BA 分别必须使用的次数在句子中。
第二行包含字符串 s(s 仅由字符 A 和 B 组成,1≤|s|≤2⋅105,|s|=a+b+2c+2d)——句子。注意条件 |s|=a+b+2c+2d(这里 |s| 表示字符串 s 的长度)等价于 s 与 a+b+c+d 的串联一样长。字。
所有测试用例的 s 长度之和不超过 2⋅105。
输出
对于每个测试用例,如果句子 s 可能恰好由 a 单词 A、b 单词 B、c 单词 AB 和 d 单词 BA 组成,则输出 YES,否则输出 NO。您可以在任何情况下输出每个字母。
思路:
我们首先先统计这个字符串中a的数目,然后会发现a,ab,ba全部都带着一个a,那么在字符串中的a的数目一定要等于三种的总数之和,这是我们先考虑单个的要符合的前提。
其次,我们开始贪心考虑两个的,我们可以设立这样一串作为子链。也就是类似于abababa这种的,或者bababa这种两两不同的。那么在遇到这样的子链的时候,我们可以使用贪心的思想来构造。首先我们一定要保证a和b尽量的多,因为a和b可以构成任意的ab或者ba。其次,我们应该明白的一点最重要的东西就是。如果想要存在这样的构造,那么首先我们需要的ab和ba可以被a和b字母替换掉某些序列的时候一定要使被替换之后能够小于等于我们拥有的ab的值,如果说ababa,因为长度是奇数,而且无论你是变成ab还是ba都不影响结果,因为你要么删左边的a,要么删右边的a,删左边就是baba,删右边都是abab,所以说这个没啥关系,不存在一定需要ab还是ba还是a和b构成。如果长度是偶数,那么你只能是abab,baba这种的先构成,然后找机会反悔一波,把某些长的序列改变成另外一方和b和a的构成。每次改变一对至少需要一对a和一对b来改变。那么为什么一定能构成呢。这个问题很简单,假如abab,那么我们没有ab的话,那么我们只要两对a对b,或者一对ab和一个ba就行了,而既然不存在ab的话,那么剩下的要想有这么多a和这么多b,那么ba或者a单个b单个数目一定要够得。所以先进行贪心构造ab和ba,反正待会还能转化,把他们贪心构造的长度记下来,方便我们待会可以进行反悔。
那么要开始反悔的时候,我们只需要sort一遍或者一开始就使用优先队列维护最大值,然后一步步的贪心处理掉最大的,为什么呢,不怕删过头了?因为大的序列我们只要往中间哪里插入他就可以变成小序列了,但是小序列不能变大,因此我们只需要变小就行,所以如果贪心变化完这个还是大,就寄。
另外一个思路相同。
看不懂可以看代码,代码的话有注释啥的。。。。
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
char s[200005];
vector<int> v1,v2;
int a,b,c,d;
bool check(int num,int cnt,int f,int num1){//cnt是ab的数量,num是有多少对a和b可以变化乱搞
if(f==1){
sort(v1.begin(),v1.end());
while(!v1.empty()&&num>0){
num--;
cnt-=v1.back();
v1.pop_back();
}
if(cnt>num1)
return 0;
else
return 1;
}
else{
sort(v2.begin(),v2.end());
while(v2.size()&&num>0){
num--;
cnt-=v2.back();
v2.pop_back();
}
if(cnt>num1)
return 0;
else
return 1;
}
}
bool solve(){
int n=strlen(s+1);
v1.clear();
v2.clear();
int cnta=count(s+1,s+1+n,'A');
if(cnta!=a+c+d)
return 0;
int cnt1=0;int cnt2=0;
for(int i=1;i<=n;i++){
int j=i;
while(j+1<=n&&s[j]!=s[j+1])
j++;//就是找到交错不同的才能用ab来应付
if((j-i+1)%2!=0){//也就是说这个字符串是奇数的话,那么头和尾相同,那么无论是变成ab还是变成ba都是将第一个字符整完
if(s[i]=='A')
a--;
else
b--;
}
else{//如果这个是偶数的话就变成了了ab或者可以整一个ba,所以说这个是可变得,我们待会再贪心把它捞出来。。(怎么那么像反悔贪心)
//为什么不用单个呢,也可以用,但是的话呢,已经被包括在变成另外一种字符的里面了。
int len=(j-i+1)/2;
if(s[i]=='A'){
cnt1+=len;
v1.push_back(len);//可以变成ba的量,也就是非必须
}
else{
cnt2+=len;
v2.push_back(len);
}
}
i=j;//遍历过这段区间就要跳过这段区间
}
if(a<0||b<0)
return 0;
return check(min(a,b),cnt1,1,c)&&check(min(a,b),cnt2,2,d);
}
signed main(){
ios::sync_with_stdio(false);
int t;
cin>>t;
while(t--){
cin>>a>>b>>c>>d;
cin>>s+1;
if(solve())
cout<<"YES\n";
else
cout<<"NO\n";
}
}