1. 程式人生 > >【做題】CF239E. k-d-sequence——線段樹

【做題】CF239E. k-d-sequence——線段樹

mark seq div 相等 ++ nlog 分時 != tmp

首先,容易得到判斷一個子串為“good k-d sequence”的方法:

  • 子串中沒有重復元素,且所有元素模d相等。
  • 記mx為除以d的最大值,mn為除以d的最小值,則\(mx-mn<=r-l+k\)

然後,我們對於每一段極大的元素同模的子串,處理\(d=1\)的情況。
顯然,我們需要枚舉一個端點。這裏,我們從大到小枚舉左端點。(當然,從小到大枚舉右端點也是可行的)
我們使用單調棧和線段樹,可以維護每個位置\(mx-mn\)的值。然後,因為對於每一個位置,\(r\)是固定的,所以我們把\(r\)移到左邊。即有不等式\(mx-mn-r<=k-l\)
然後,我們需要確定最右邊的\(mx-mn-r<=k-l\)

的元素位置,這個線段樹上二分就可以了。
最後還有兩個細節:

  • 為避免出現重復元素,線段樹上二分時有限制。
  • 特判\(d=0\)的情況。

時間復雜度\(O(nlogn)\)

#include <bits/stdc++.h>
using namespace std;
const int BAS = 1e9, N = 200010;
struct node {
  int mn,tag;
  inline void operator += (int x) {
    mn += x;
    tag += x;
  }
  inline void reset() {
    mn = tag = 0
; } } t[N << 2]; void push_down(int x) { t[x<<1] += t[x].tag; t[x<<1|1] += t[x].tag; t[x].tag = 0; } void push_up(int x) { if (t[x].tag) push_down(x); t[x].mn = min(t[x<<1].mn,t[x<<1|1].mn); } void modify(int x,int l,int r,int v,int lp,int rp) { if (lp > r || rp < l) return
; if (lp >= l && rp <= r) return (void)(t[x] += v); int mid = (lp + rp) >> 1; modify(x<<1,l,r,v,lp,mid); modify(x<<1|1,l,r,v,mid+1,rp); push_up(x); } int dfs(int x,int lim,int v,int lp,int rp) { if (t[x].mn > v) return -1; if (lp == rp) return lp; push_down(x); int mid = (lp + rp) >> 1; if (t[x<<1|1].mn <= v && mid + 1 <= lim) { int res = dfs(x<<1|1,lim,v,mid+1,rp); if (~res) return res; } return dfs(x<<1,lim,v,lp,mid); } int n,k,d,arr[N],len; map<int,int> mp; int tmp[N]; struct data_sta { int l,r,val; inline bool operator < (const data_sta& x) const { return val < x.val; } } st[2][N]; int top[2]; struct data_ans { int l,r; inline bool operator < (const data_ans& x) const { return r - l + 1 != x.r - x.l + 1 ? r - l + 1 > x.r - x.l + 1 : l < x.l; } }; data_ans solve() { mp.clear(); data_sta tp; data_ans res = (data_ans) {len,-1}; int cur = len, rec; top[0] = top[1] = 0; for (int i = len ; i >= 1 ; -- i) { if (mp[tmp[i]]) cur = min(cur,mp[tmp[i]] - 1); mp[tmp[i]] = i; tp = (data_sta) {i,i,tmp[i]}; while (top[0] && st[0][top[0]].val < tp.val) { modify(1,st[0][top[0]].l,st[0][top[0]].r,-st[0][top[0]].val,1,len); tp.r = st[0][top[0]--].r; } st[0][++top[0]] = tp; modify(1,tp.l,tp.r,tp.val,1,len); tp = (data_sta) {i,i,tmp[i]}; while (top[1] && st[1][top[1]].val > tp.val) { modify(1,st[1][top[1]].l,st[1][top[1]].r,st[1][top[1]].val,1,len); tp.r = st[1][top[1]--].r; } st[1][++top[1]] = tp; modify(1,tp.l,tp.r,-tp.val,1,len); modify(1,i,i,-i,1,len); rec = dfs(1,cur,k - i,1,len); if (~rec) res = min(res,(data_ans) {i,rec}); } for (int i = 1 ; i <= (len << 2) ; ++ i) t[i].reset(); return res; } int special_solve() { int res = 0, p = -1; for (int i = 1, j; i <= n ; i += j) { j = 1; while (arr[i+j] == arr[i] && i + j <= n) ++ j; if (res < j) res = j, p = i; } printf("%d %d\n",p,p + res - 1); return 0; } int main() { scanf("%d%d%d",&n,&k,&d); for (int i = 1 ; i <= n ; ++ i) scanf("%d",&arr[i]), arr[i] += BAS ; if (d == 0) return special_solve(); data_ans res = (data_ans) {1,1}, tp; for (int i = 1, j ; i <= n ; i += j) { j = 1; while (arr[i+j] % d == arr[i] % d && i + j <= n) ++ j; len = j; for (int s = 0 ; s < j ; ++ s) tmp[s+1] = arr[i+s] / d; tp = solve(); tp.l += i-1, tp.r += i-1; res = min(res,tp); } printf("%d %d\n",res.l,res.r); return 0; }



小結:這樣一類題目大概就是要懟著式子簡化問題。

【做題】CF239E. k-d-sequence——線段樹