http://www.spoj.com/problems/LPERMUT/
Brief description:
给出一个序列,求最大的 mx
,使得存在一个长度为 mx
的连续子序列,使其恰好为 1..mx
的一个排列。
Analysis:
好题。。。Vijos1381 数据太弱,正解应是 O(n) 。
尽量避免使用数据结构。。。我们需要充分利用排列的性质。。。
区间 [l, r]
恰好是 1..len
(len = r-l+1)的排列的充分必要条件是:
- [l, r] 区间的数两两不同。
- s[r]-s[l+1] = 1+2+…+len。。。
为了确保区间中的数字两两不同。。。我们设 l[i] 为 i 向左最长可以延伸的距离。。(边界设 l[0] = 0, l[n+1] = n+1。。)
。。那么有 l[i] = max(l[i], p[a[i]]+1)。。。(p[a[i]] 是上一个 a[i] 的位置。。。(也可以用 two point 得到这个东西。。。。
void upd(int l, int r, int len){ if (len < ans) return; if (::l[r] <= l && (s[r]-s[l-1])*2 == (1+len)*len) ans = len; }
。序列被 1 分割成了一些小的区间。。(不能包含两个相同的 1。。。)。。
____ 1 ____ 1 _________ 1 _____
我们枚举每个 1.。。讨论区间中最大的数字 mx 出现在 1 的左边还是右边。。对称处理。。
。。考虑左边。。
for (int j=i-1,mx=1;l[i]<=j;--j){ checkMax(mx, a[j]); upd(j,j+mx-1,mx); }
由于 upd()
过程是 O(1)
的。。所以整个算法是 O(n)
的。。。
//}/* .................................................................................................................................. */ const int N = int(1e5) + 9; int a[N], s[N], l[N], p[N], n, ans; void upd(int l, int r, int len){ if (len < ans) return; if (::l[r] <= l && (s[r]-s[l-1])*2 == (1+len)*len) ans = len; } int main(){ #ifndef ONLINE_JUDGE freopen("in.txt", "r", stdin); //freopen("out.txt", "w", stdout); #endif REP_1_C(i, RD(n)) s[i] = s[i-1] + RD(a[i]); RST(p); l[0] = 0; REP_1(i, n){ l[i] = max(l[i-1], p[a[i]]+1); p[a[i]] = i; //cout << l[i] << " "; } l[n+1] = n+1; //puts(""); REP_1(i, n) if (a[i] == 1){ upd(i, i, 1); for (int j=i-1,mx=1;l[i]<=j;--j){ checkMax(mx, a[j]); upd(j,j+mx-1,mx); } for (int j=i+1,mx=1;l[j]<=i;++j){ checkMax(mx, a[j]); upd(j-mx+1,j,mx); } } OT(ans); }