题意:
给出一个长度为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多理解一下啦~