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;
}
posted @ 2022-07-05 20:53  tythen  阅读(98)  评论(0编辑  收藏  举报