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