Codeforces 232(#144 Div1) D - Fence
問題
n項からなる数列a[i]が与えられる。これに対してq個のクエリに答えよ。
クエリ: 区間l, rが指定される。
[l, r]と長さが等しく、[l, r]に交わらない区間[s, t]で、a[l + i] = a[s + i] = constとなる区間はいくつあるか求めよ。
制約条件
n, q≦10^5
a[i]≦10^9
方針
まずはa[i]の差分を取り、d[i] = a[i + 1] - a[i]とする。e[i] = -d[i]とする。
で、d[i](区切り文字)e[i]と並べた数列を文字列と見てsuffix arrayを作る。
suffix arrayの転置とLCP, LCPに対するRMQも作っておく。
クエリに対して、sa[l + n + 1] = xとなるxを転置により求める。(マイナスの符号をつけたほうの[l, r]のsuffix array上での位置を探す)
LCP上を二分探索して、x, y間のLCPの最小値がr - l以上であるような最大、最小のyを求める。
このminy〜maxyを使えば、suffix array上で[l, r]に一致する区間の先頭の添え字が{sa[i] | miny <= i <= maxy}と書ける。
この中から[l, r]に交わらないものを求めればよいので、数列の区間内でxx以上の個数を数える問題に帰着される。
つまり、range treeを作っておいて、[l, r]上でr + 1以上またはl - (r - l) - 1以下の数の個数を求めればよい。
range treeは蟻本のK番目の数字を解くのに使ったデータ構造で、
segment treeの各ノードにその区間の葉すべてを昇順に並べたものを持つやつ。
ここで二分探索をすればxx以上の個数は効率的に求められる。
range treeに最初全てのノードを入れてしまうとマイナスの区間の要素(e[i]由来のインデックス)もカウントしてしまうので、葉の初期化のときにはd[i]由来のやつのときだけ代入するようにする。
ソースコード
range treeのとこは挿入順をがんばってクエリをソートしてってやればsenment treeでもできてオーダーもO(qlog n)になる(けどめんどかった)。
int a[MAX_LEN], b[MAX_LEN]; const int N = 262144; vi dat[2 * N - 1]; inline int qu(int a, int b, int k, int l, int r, int lo, int hi){//lo以下or, hi以上 if(a <= l && r <= b) return (upper_bound(all(dat[k]), lo) - dat[k].begin()) + (dat[k].end() - lower_bound(all(dat[k]), hi)); if(a >= r || b <= l) return 0; int x = qu(a, b, k * 2 + 1, l, (l + r) / 2, lo, hi); int y = qu(a, b, k * 2 + 2, (l + r) / 2, r, lo, hi); return x + y; } int main(){ int m; scanf("%d", &m); vi v(m), d; rep(i, m){ scanf("%d", &v[i]); if(i) d.pb(v[i] - v[i - 1]); } v = d; n = m - 1; rep(i, n) v.pb(-d[i]); sort(all(v)); v.erase(unique(all(v)), v.end()); rep(i, n){ a[i] = lower_bound(all(v), d[i]) - v.begin() + 2; a[i + n + 1] = lower_bound(all(v), -d[i]) - v.begin() + 2; } a[n] = 1; n = 2 * n + 1; buildSA(a); buildLCP(a); int *rmq = buildRMQ(lcp, n + 1); rep(i, n + 1) if(si[i] < m - 1) dat[i + N - 1].pb(si[i]); for(int i = N - 2; i >= 0; i--){ int l = i * 2 + 1, r = i * 2 + 2; dat[i].resize(dat[l].size() + dat[r].size()); merge(all(dat[l]), all(dat[r]), dat[i].begin()); } int q; scanf("%d", &q); while(q--){ int l, r; scanf("%d%d", &l, &r); if(l == r){ printf("%d\n", m - 1); continue; } l--; r--; int t = is[l + m], x, y; int lo = -1, hi = t, mid; while(lo + 1 < hi){ mid = (lo + hi) / 2; if(minimum(mid + 1, t, rmq, n + 1) >= r - l) hi = mid; else lo = mid; } x = hi; lo = t, hi = n + 1, mid; while(lo + 1 < hi){ mid = (lo + hi) / 2; if(minimum(t + 1, mid, rmq, n + 1) >= r - l) lo = mid; else hi = mid; } y = hi; int ans = qu(x, y, 0, 0, N, l - (r - l) - 1, r + 1); printf("%d\n", ans); } return 0; }