从今以后

题意:

给出一个长度为n(<=1e5)的序列,判断这个序列否合法,它任意区间总有一个元素只在这个区间出现一次。

 

题解:

方法一:

对于n^2的暴力是很好想的,枚举一个起点i,一直到j,维护一个类似于前缀和的东西,表示i到j中有几个数只出现了一次,如果为0那么就不合法。

因为j 的枚举是从i ~ n的,所以很容易就优化下去了。

那么现在已经算出了1~i中出现一次的数个数,现在考虑将最前面的数删去,那么会对后面造成影响,怎么在第一次的基础上得到而不是重新计算呢?

设last[i]表示xi[i]这个数后面第一个相同数字的位置,设最前面的数的编号为i,那么对于[i + 1,last[i] - 1]这个区间应该减1,对于[last[i],last[last[i]] - 1]这个区间加1,后面的区间不变。

那么只需要维护一个最小值就好了,如果这个最小值为0,那么就不合法了。

 

代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 7;
int mini[N << 2], lazy[N << 2];
int kase, n, xi[N], disc[N], dcnt, ans[N], cnt[N], id[N], last[N];

#define mid (l + r >> 1)
#define lson o << 1, l, mid
#define rson o << 1 | 1, mid + 1, r
#define clr(a, b) memset (a, b, sizeof a)
void pushdown (int o)
{
	if (!lazy[o]) return;
	mini[o << 1]  += lazy[o], lazy[o << 1] += lazy[o];
	mini[o << 1 | 1] += lazy[o], lazy[o << 1 | 1] += lazy[o];
	lazy[o] = 0;
}

int query (int o, int l, int r, int L, int R)
{
	if (l == L && r == R) return mini[o];
	pushdown(o);
	if (R <= mid) return query (lson, L, R);
	else if (L > mid) return query (rson, L, R);
	else return min (query(lson, L, mid), query(rson, mid + 1, R));
}

void update (int o, int l, int r, int L, int R, int x)
{
	if (l == L && r == R)
	{
		mini[o] += x;
		lazy[o] += x;
		return;
	}
	pushdown (o);
	if (R <= mid) update (lson, L, R, x);
	else if (L > mid) update (rson, L, R, x);
	else update (lson, L, mid, x), update (rson, mid + 1, R, x);
	mini[o] = min (mini[o << 1], mini[o << 1 | 1]);
}

void build (int o, int l, int r)
{
	if (r == l) 
	{
		mini[o] = ans[l];
		return;
	}
	build (lson);
	build (rson);
	mini[o] = min (mini[o << 1], mini[o << 1 | 1]);
}

int main()
{
	scanf ("%d", &kase);
	while (kase --)
	{
		dcnt = 0;
		clr(cnt, 0), clr(ans, 0), clr(mini, 0), clr(lazy, 0), clr (id, 0), clr(last, 0);
		scanf ("%d", &n);
		for (int i = 1; i <= n; ++ i)
		{
			scanf ("%d", &xi[i]);
			disc[++dcnt] = xi[i];
		}
		sort (disc + 1, disc + 1 + dcnt);
		dcnt = unique (disc + 1, disc + 1 + dcnt) - disc - 1;
		for (int i = n; i >= 1; -- i)
		{
			xi[i] = lower_bound (disc + 1, disc + 1 + dcnt, xi[i]) - disc;
			if (id[xi[i]]) last[i] = id[xi[i]];
			else last[i] = n + 1;
			id[xi[i]] = i;
		}
			
		int getans = 0;
		for (int i = 1; i <= n; ++ i)
		{
			cnt[xi[i]]++;
			if (cnt[xi[i]] == 1) ans[i] = ans[i - 1] + 1;
			else if (cnt[xi[i]] == 2) ans[i] = ans[i - 1] - 1;
			else ans[i] = ans[i-1];
			if (ans[i] == 0) {printf ("no\n"); getans = 1; break;}
		}
		if (getans) continue;
		build (1, 1, n);
		for (int i = 2; i <= n; ++ i)
		{
			update (1, 1, n, i, last[i - 1] - 1, -1);
			if (last[i - 1] <= n) update (1, 1, n, last[i - 1], last[last[i - 1]] - 1, 1);
			if (query(1, 1, n, i, n) == 0) {printf ("no\n"); getans = 1; break;}
		}
		if (!getans){printf ("yes\n"); getans = 1;}
	}
	return 0;
}

  

这个是我一开始想的比较蠢的算法了,因为是从暴力优化来的嘛~

 

方法二:

对于一个区间[L,R]判断它合法,是有一个元素在这个区间只出现了一次,那么怎么判断一个元素在这个区间是否出现过一次呢?那么需要找到这个元素的前驱和后继的位置,判断他们是否在这个区间内,如果在那么这个元素在这个区间出现不止一次,如果这个区间合法那么就继续验证它的子区间,复杂度什么的不会证明QAQ

 

代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 7;
int id[N], L[N], R[N], disc[N], dcnt, xi[N], kase, n;

void process()
{
	memset (id, 0, sizeof id);
	for (int i = 1; i <= n; ++i)
	{
		if (id[xi[i]]) L[i] = id[xi[i]];
		else L[i] = 0;
		id[xi[i]] = i;
	}
	memset (id, 0, sizeof id);
	for (int i = n; i >= 1; --i)
	{
		if (id[xi[i]]) R[i] = id[xi[i]];
		else R[i] = n + 1;
		id[xi[i]] = i;
	}
}

int check (int l, int r)
{
	if (l >= r) return true;
	for (int i = l, j = r; i <= j; ++ i, -- j)
	{
		if (L[i] < l && R[i] > r) return check (l, i - 1) && check (i + 1, r);
		if (L[j] < l && R[j] > r) return check (l, j - 1) && check (j + 1, r);
	}
	return false;
}

int main ()
{
	scanf ("%d", &kase);
	while (kase --)
	{
		dcnt = 0;
		scanf ("%d", &n);
		for (int i = 1; i <= n; ++ i)
		{
			scanf ("%d", &xi[i]);
			disc[++dcnt] = xi[i];
		}
		sort (disc + 1, disc + 1 + dcnt);
		dcnt = unique (disc + 1, disc + 1 + dcnt) - disc - 1;
		for (int i = 1; i  <= n; ++i)
			xi[i] = lower_bound (disc + 1, disc + 1 + dcnt, xi[i]) - disc;
		process();
		check (1, n) ? puts("yes") : puts("no"); 
	}
	return 0;
}

  

总结:

对于这种套路感觉有点像meet in the middle 好神啊,这种做法QAQ多理解一下啦~

转载于:https://www.cnblogs.com/xgtao/p/6020766.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值