D - RGB Triplets
题目:
一长为 n 的字符串由 RBG 组成,找出满足以下条件的三元组 (i,j,k) 的数量:
1. i < j < k
2. s[i] != s[j] && s[i] != s[k] && s[j] != s[k]
3. j−i != k−j
数据范围:
1 ≤ N ≤ 4000
思路1:
先算出满足条件1,2的个数,再减去其中不满足条件3的个数。
满足条件1,2的个数即任意选三个不同的字符的个数;再减去满足 j - i = k - j 的个数。
预处理出3种字符的个数a,b,c,总数即为a*b*c;再枚举前两个端点的位置 i,j,判断第三个点的位置 k = 2 * j - i 是否与前两个都不同且满足 j -i != k - j。总数减去不满足的个数。
Code:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
#define x first
#define y second
#define int long long
const int N = 200010, INF = 0x3f3f3f3f;
typedef pair<int, int>PII;
int n;
string s;
int a, b, c;
void solve()
{
cin >> n >> s;
for (int i = 0; i < n; i++) //记录3种字符的个数
{
if (s[i] == 'R')a++;
if (s[i] == 'G')b++;
if (s[i] == 'B')c++;
}
int res = a * b * c; //满足条件1的总数
//枚举所选的前两个字符
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++)
{
if (s[i] == s[j])continue;
if (2 * j - i < n && s[i] != s[2 * j - i] && s[j] != s[2 * j - i]) //第三个字符的位置即中间字符的位置*2-第一个字符的位置:j*2-i
res--; //将不满足条件2的个数减去
}
cout << res << endl;
}
signed main()
{
int t = 1;
//cin >> t;
while (t--)
{
solve();
}
return 0;
}
思路2:
将3种字符的位置单独存一块,边枚举边判断。
将 3 种字符的位置单独存(vector[a][]存的是a类字符占据的位置),按取排列中 3 个字符时的先后顺序共枚举9中情况。先后顺序一定时,再顺次枚举每个字符的位置:
第一个字符的位置全枚举;第二个字符的可取位置通过二分查找,从第一个满足位置 j > i 的下标开始枚举;第三个字符的可取位置也用二分查找,从第一个满足位置 k > j 的下标开始枚举。
确定 i, j, k 后再判断是否满足j - i != k - j。如果满足,才说明此时的选法可行。
Code:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
#define x first
#define y second
#define int long long
const int N = 200010, INF = 0x3f3f3f3f, mod = 1e9 + 7;
typedef pair<int, int>PII;
int n;
string s;
vector<int>v[3];
//在b类字符占据的位置中找第一个大于v[a][i]的位置,返回在b数组中对应的下标,即在v[b]中找第一个大于v[a][i]的值对应的下标
int idx(int b, int a, int i)
{
return upper_bound(v[b].begin(), v[b].end(), v[a][i]) - v[b].begin();
}
//记录按a,b,c的先后顺序的可行的排列数
int cal(int a, int b, int c)
{
int ans = 0;
//枚举3种字符的位置
for(int i = 0;i < v[a].size(); i++) //枚举第一个字符的位置在数组v[a]中的下标
for(int j = idx(b,a,i); j < v[b].size(); j++) //枚举位置在i之后的第二个字符的位置在数组v[b]中的下标
for (int k = idx(c,b,j); k < v[c].size(); k++) //枚举位置在j之后的第三个字符的位置在数组v[c]中的下标
{
if (v[b][j] - v[a][i] != v[c][k] - v[b][j]) //这里的v[a][i]表示a类字符存位置的数组中下标为i时对应的s中的位置。等价于判断 j-i != k-j是否成立
ans++;
}
return ans;
}
void solve()
{
cin >> n >> s;
for (int i = 0; i < n; i++) //记录不同字符的位置
{
if (s[i] == 'R')v[0].push_back(i);
if (s[i] == 'G')v[1].push_back(i);
if (s[i] == 'B')v[2].push_back(i);
}
int res = 0;
//按3种字符的先后顺序枚举
for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++)
for (int k = 0; k < 3; k++)
{
if (i != j && i != k && j != k) //枚举到3个位置的字符不同时
res += cal(i, j, k); //累计这种先后顺序的可行方案数
}
cout << res << endl;
}
signed main()
{
int t = 1;
//cin >> t;
while (t--)
{
solve();
}
return 0;
}
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
吐槽:开始没想到k可以用2*j-1表示,想着单独处理,奈何能力不够写不出来。题目注意点:
1.当三个数间距相同时,第二个数相当于中点嘛,只要直到两个数的位置即可求出第三个点的位置。
2.注意动态数组用upper_bound()时的形式。第二种方法在确认后两个字符的位置时是存在一个转换的,因为要枚举位置,只能通过枚举存位置的数组的下标来。在存位置的数组中先找到符合的位置,但是返回的是下标,再用数组v[][]得出在s中的位置,进而才能比较。