【USACO25OPEN】It’s Mooin’ Time III B
USACO 专栏:USACO 2025 OPEN
算法竞赛:二分,二分查找,枚举
题目链接:洛谷 P12024 [USACO25OPEN] It’s Mooin’ Time III B
题目描述:
Elsie 正在试图向 Bessie 描述她最喜欢的 USACO 竞赛,但 Bessie 很难理解为什么 Elsie 这么喜欢它。Elsie 说「现在是哞哞时间!谁想哞哞?请,我只想参加 USACO」。
Bessie 仍然不理解,于是她将 Elsie 的描述转文字得到了一个长为 N N N( 3 ≤ N ≤ 1 0 5 3 \leq N \leq 10^5 3≤N≤105)的字符串,包含小写字母字符 s 1 s 2 … s N s_1s_2 \ldots s_N s1s2…sN。Elsie 认为一个包含三个字符的字符串 t t t 是一个哞叫,如果 t 2 = t 3 t_2 = t_3 t2=t3 且 t 2 ≠ t 1 t_2 \neq t_1 t2=t1。
一个三元组 ( i , j , k ) (i, j, k) (i,j,k) 是合法的,如果 i < j < k i < j < k i<j<k 且字符串 s i s j s k s_i s_j s_k sisjsk 组成一个哞叫。对于该三元组,FJ 执行以下操作计算其值:
- FJ 将字符串 s s s 在索引 j j j 处弯曲 90 度
- 该三元组的值是 Δ i j k \Delta ijk Δijk 的面积的两倍。
换句话说,该三元组的值等于 ( j − i ) ( k − j ) (j-i)(k-j) (j−i)(k−j)。
Bessie 向你进行 Q Q Q( 1 ≤ Q ≤ 3 ⋅ 1 0 4 1 \leq Q \leq 3 \cdot 10^4 1≤Q≤3⋅104)个查询。在每个查询中,她给你两个整数 l l l 和 r r r( 1 ≤ l ≤ r ≤ N 1 \leq l \leq r \leq N 1≤l≤r≤N, r − l + 1 ≥ 3 r-l+1 \ge 3 r−l+1≥3),并询问满足 l ≤ i l \leq i l≤i 和 k ≤ r k \leq r k≤r 的所有合法三元组 ( i , j , k ) (i, j, k) (i,j,k) 的最大值。如果不存在合法的三元组,输出 − 1 -1 −1。
注意这个问题涉及到的整数可能需要使用 64 位整数类型(例如,C/C++ 中的long long)。
输入格式:
输入的第一行包含两个整数 N N N 和 Q Q Q。
以下一行包含 s 1 s 2 , … s N s_1 s_2, \ldots s_N s1s2,…sN。
以下 Q Q Q 行每行包含两个整数 l l l 和 r r r,表示每个查询。
输出格式:
对于每一个查询输出一行,包含对于该查询的答案。
奶牛时间(三) It’s Mooin’ Time III B
题目大意
一个包含三个字符的字符串 t [ t 1 , t 2 , t 3 ] t[t_1,t_2,t_3] t[t1,t2,t3],如果 t 2 = t 3 t_2 = t_3 t2=t3 且 t 2 ≠ t 1 t_2 \neq t_1 t2=t1,则字符串 t t t 是一个哞叫。
一个三元组 ( i , j , k ) (i, j, k) (i,j,k),如果 i < j < k i < j < k i<j<k 且字符串 s i s j s k s_i s_j s_k sisjsk 组成一个哞叫,则这个三元组是合法的,该三元组的值等于 ( j − i ) ( k − j ) (j-i)(k-j) (j−i)(k−j)。
输入一个长为 N N N( 3 ≤ N ≤ 1 0 5 3 \leq N \leq 10^5 3≤N≤105)的字符串,包含小写字母字符 s 1 s 2 … s N s_1s_2 \ldots s_N s1s2…sN。
接下来会有 Q Q Q( 1 ≤ Q ≤ 3 × 1 0 4 1 \leq Q \leq 3 \times 10^4 1≤Q≤3×104)个查询。在每个查询中,给出两个整数 l l l 和 r r r( 1 ≤ l ≤ r ≤ N 1 \leq l \leq r \leq N 1≤l≤r≤N, r − l + 1 ≥ 3 r-l+1 \ge 3 r−l+1≥3),并询问满足 l ≤ i l \leq i l≤i 和 k ≤ r k \leq r k≤r 的所有合法三元组 ( i , j , k ) (i, j, k) (i,j,k) 的最大值。如果不存在合法的三元组,输出 − 1 -1 −1。
题目分析
这道题我们采用贪心策略和二分优化,在区间 l ∼ r l\sim r l∼r 中,我们枚举这个区间中所出现过的字符 c c c,找两个位置 i , j i,j i,j,使 s i = s j = c s_i=s_j=c si=sj=c,则 s i , s j s_i,s_j si,sj 就充当 t 2 , t 3 t_2,t_3 t2,t3,对于 t 2 , t 3 t_2,t_3 t2,t3,我们可以确定 t 3 t_3 t3 应为区间 l ∼ r l\sim r l∼r 中最后一个字符 c c c,而 t 2 t_2 t2 应从区间 l ∼ r l\sim r l∼r 第一个字符 c c c 枚举到最后一个字符 c c c 之前,在此过程中, t 1 t_1 t1 一直不变,即为区间 l ∼ r l\sim r l∼r 中第一个不为 c c c 的字符,每次取最大值更新答案。
解法步骤
- 将给出的字符串中出现的字符分类,统计每种字符出现的位置,在每次询问中,执行以下操作:
- 给出区间 l ∼ r l\sim r l∼r,遍历字符串中出现的每种字符 c c c(即使在区间 l ∼ r l\sim r l∼r 中没有出现过也无妨);
- 找到区间 l ∼ r l\sim r l∼r 中第一个与 c c c 不相同的字符的位置,记录为 s i t e 1 site_1 site1(枚举即可);
- 找到区间 l ∼ r l\sim r l∼r 中最后一个 c c c 字符,记录其位置为 s i t e 3 site_3 site3,找到区间 l ∼ r l\sim r l∼r 中第一个 c c c 字符,记录其指针为 i t it it,指针 i t it it 向后枚举(字符 c c c 出现的位置);
- i t it it 所指的位置即为 s i t e 2 site_2 site2,本次得到的三元组的值为 n u m = ( s i t e 3 − s i t e 2 ) × ( s i t e 2 − s i t e 1 ) num=(site_3-site_2)\times (site_2-site_1) num=(site3−site2)×(site2−site1),更新结果 a n s = max ( a n s , n u m ) ans=\max(ans,num) ans=max(ans,num)。
代码指导
- 可以使用 vector 容器储存每种字符 c c c 出现的位置,以方便二分查找。
- 可以使用语句
auto it=upper_bound(G[id].begin(),G[id].end(),l);来找到区间中字符 c c c 第一次出现的位置。 - 可以使用语句
auto itt=upper_bound(G[id].begin(),G[id].end(),r);itt--;来找到区间中字符 c c c 最后一次出现的位置。 - 在
t
2
t_2
t2 位置枚举过程中,可以添加语句
if ((long long)d*_d<=num) break;,表示如果当前的 ( s i t e 3 − s i t e 2 ) × ( s i t e 2 − s i t e 1 ) (site_3-site_2)\times (site_2-site_1) (site3−site2)×(site2−site1) 小于或等于已有的 n u m num num 就可以不再继续枚举,因为 t 2 t_2 t2 位置越靠中间三元组的值越大。
AC Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
char s[N];
vector<int>G[N],K;
map<char,int>mp;
int main()
{
int n,q,id=0;
scanf("%d%d",&n,&q);
scanf("%s",s+1);
for (int i=1; i<=n; i++)
{
if (!mp[s[i]]) mp[s[i]]=++id;
G[mp[s[i]]].push_back(i);
}
for (int i=1; i<=n; i++) K.push_back(mp[s[i]]);
sort(K.begin(),K.end());
K.erase(unique(K.begin(),K.end()),K.end());
while (q--)
{
int l,r;
long long ans=-1;
scanf("%d%d",&l,&r);
for (int id : K)
{
long long num=-1;
auto it=upper_bound(G[id].begin(),G[id].end(),l);
auto itt=upper_bound(G[id].begin(),G[id].end(),r);
itt--;
int e=l;
while (mp[s[e]]==id) e++;
while (it!=itt&&it!=G[id].end()&&*it<=e) it++;
while (it!=itt&&it!=G[id].end())
{
int d=*it-e;
int _d=*itt-*it;
if ((long long)d*_d<=num) break;
num=max(num,(long long)d*_d);
it++;
}
ans=max(ans,num);
}
printf("%lld\n",ans);
}
return 0;
}
时间复杂度
有 q q q 次询问,枚举字符 c c c 有 C = 26 \text{C}=26 C=26 种, t 2 t_2 t2 枚举的次数为 n n n,时间复杂度为 O ( C ⋅ q n ) \mathcal{O(\text{C}\cdot qn)} O(C⋅qn)。
博客说明
本 优快云 博客最早发布于洛谷文章广场,作者:ZZA000HAH,与 2025/11/16 同步至 优快云 博客平台。
USACO Mooing Time III 问题解析
416

被折叠的 条评论
为什么被折叠?



