codeforces 1692H - Gambling
题意:
选择一个数,求一个区间,使这个数的出现次数 减去 其它数出现次数 最大。
分析:
把某个数看成 1 和 -1 我想到了,但是没法解决固定这么多不同的数的问题。
所以这个分类做前缀和再找最大的操作还是有点牛。用到一个map把固定的数字的出现下标存下来,然后转化成1和-1的模型,做前缀和。
这个前缀和很特化,不能被模型思维束缚,要会利用前缀和性质变通
注意这个遍历map的操作,可以用iterator来写,也可以用auto
剩下的细节代码注释写的很清楚
code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
int n;
int a[N];
void solve()
{
map <int, vector<int>> M;//把各个数分类开,存下标
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
M[a[i]].push_back(i);
}
int ans = 1, anskey = a[1], ansl = 1, ansr = 1;//初始答案先设置成第一个数
//for (map <int, vector<int>>::iterator = M.begin(); it != M.end(); ++it)此时 key 就是 it 的first,v就是second
for (auto [key, v] : M)
{
//接下来做一个用下标优化的前缀和,再从前缀和里面统计出最大的子段
vector <int> sum(v.size());
sum[0] = 1;//v里每个数代表一个下标,也就是位置,sum[i]表示v[i]这个位置的前缀和
for (int i = 1; i < v.size(); i++)
{
sum[i] = sum[i-1] - (v[i] - v[i-1] - 1) + 1;//两个位置中间夹住的部分都是-1
}
int j = 0;//j保存的都是更小的那个前缀,也就是左端点,这样遍历i作为右端点,找到最大子段
for (int i = 1; i < v.size(); i++)
{
if (ans < sum[i] - sum[j] + 1)//这种特化的前缀和不能访问j-1,只能手动补上+1
{
ans = sum[i] - sum[j] + 1;
ansl = v[j], ansr = v[i];
anskey = key;
}
if(sum[j] > sum[i]) j = i;//始终保存最小的前缀
}
}
cout << anskey << ' ' << ansl << ' ' << ansr << '\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while (t--)
{
solve();
}
return 0;
}