浙江农林大学第十九届程序设计竞赛 G题 ( 牛客 7872G )

本文解析了一道算法题,目标是在给定序列中找到一个连续子段,使得该子段内只出现一次的数字数量达到最大。通过将问题转换为寻找每个右端点对应的最优左端点,利用区间更新和区间查询的方法进行高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:

https://ac.nowcoder.com/acm/contest/7872/G

题目大意:

给定一个长度为n的序列,需要找出一段最优的连续子段,使得出现在子段中出现次数为1次的数字最多(多一次少一次都不行,只要一次)。
求最优连续子段中出现次数为1次的数字个数。

样例:

输入
4
1 2 1 2
输出
2

思路:

对于一个序列求其最优连续子序列问题,我们可以将其变成:
对于一个固定了右端点的连续子序列,求其最优左端点的问题
那么从左到右,我们每让右端点右移一个元素,就求一遍最优左端点,
对于每次右移,我们可以把左端点分为三类:
(设每次左端点为s,当前右端点为e,第i个位置的元素值为x[i])
第一类,由s到e - 1之内没有出现过x[e]。
第二类,由s到e - 1之内出现过一次x[e]。
第三类,由s到e - 1之内出现过二次及以上的x[e]。

步骤一:对于第一类及第一类以前(第二类和第三类)的s,
我们先假设x[e]都对于其贡献了一次答案,故全部+1

步骤二:对于第二类的s由于x[e]已经不是第一次出现了
那么x[e]和之前x[s]贡献的答案全部要剪掉故全部-2

步骤三:对于第三类的s由于x[e]已经出现过两次,那么其x[s]的贡献在
之前(当这些s曾经为第二类时)就已经被剪掉过 故+1(步骤二多减了)

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 5;
int n, px1[maxn], px2[maxn], px3[maxn], xx[maxn];
int tree[maxn << 2];
int treeLaze[maxn << 2];

void update(int ii, int cntL, int cntR, int tL, int tR, int xx);
int readTree(int ii, int cntL, int cntR, int tL, int tR);
void mod1(int ii);
void mod2(int ii);
void mod3(int ii);

int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%d", xx + i);
	int res = 0;
	for (int i = 1; i <= n; i++) {
		mod1(i);
		mod2(i);
		mod3(i);
		px3[xx[i]] = px2[xx[i]];
		px2[xx[i]] = px1[xx[i]];
		px1[xx[i]] = i;
		int tres = readTree(1, 1, n, 1, i);
		res = max(tres, res);
	}
	printf("%d\n", res);
	return 0;
}

void pushDw(int ii, int cntL, int cntR)
{
	tree[ii] += treeLaze[ii];
	if (cntL != cntR) {
		treeLaze[ii << 1] += treeLaze[ii];
		treeLaze[ii << 1 | 1] += treeLaze[ii];
	}
	treeLaze[ii] = 0;
}

void pushUp(int ii, int cntL, int cntR)
{
	int cntM = (cntL + cntR) >> 1;
	if (treeLaze[ii << 1]) pushDw(ii << 1, cntL, cntM);
	if (treeLaze[ii << 1 | 1]) pushDw(ii << 1 | 1, cntM + 1, cntR);
	tree[ii] = max(tree[ii << 1], tree[ii << 1 | 1]);
}

void update(int ii, int cntL, int cntR, int tL, int tR, int xx)
{
	pushDw(ii, cntL, cntR);
	if (cntL == tL && cntR == tR) {
		treeLaze[ii] += xx;
		return;
	}
	int cntM = (cntL + cntR) >> 1;
	if (tR <= cntM) update(ii << 1, cntL, cntM, tL, tR, xx);
	else if (tL > cntM) update(ii << 1 | 1, cntM + 1, cntR, tL, tR, xx);
	else {
		update(ii << 1, cntL, cntM, tL, cntM, xx);
		update(ii << 1 | 1, cntM + 1, cntR, cntM + 1, tR, xx);
	}
	pushUp(ii, cntL, cntR);
}

int readTree(int ii, int cntL, int cntR, int tL, int tR)
{
	pushDw(ii, cntL, cntR);
	if (cntL == tL && cntR == tR) {
		return tree[ii];
	}
	int cntM = (cntL + cntR) >> 1;
	if (tR <= cntM) return readTree(ii << 1, cntL, cntM, tL, tR);
	else if (tL > cntM) return readTree(ii << 1 | 1, cntM + 1, cntR, tL, tR);
	else {
		int tresL = readTree(ii << 1, cntL, cntM, tL, cntM);
		int tresR = readTree(ii << 1 | 1, cntM + 1, cntR, cntM + 1, tR);
		
		return max(tresL, tresR);
	}
}

void mod1(int ii)
{
	static int L;
	static int R;
	L = 1;
	R = ii;
	if (L > R) return;
	update(1, 1, n, L, R, 1);
}

void mod2(int ii)
{
	static int L;
	static int R;
	L = 1;
	R = px1[xx[ii]];
	if (L > R) return;
	update(1, 1, n, L, R, -2);
}

void mod3(int ii)
{
	static int L;
	static int R;
	L = 1;
	R = px2[xx[ii]];
	if (L > R) return;
	update(1, 1, n, L, R, 1);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值