[CF407E]k-d-sequence
壹、題目描述 ¶
貳、題解 ¶
一般情形,如果 \(d\neq 0\),那麼一段區間 \([L,R]\) 合法的基礎條件是
\[\forall i,j\in [L,R],a_i\equiv a_j\pmod d\;\and\;\forall i,j\in[L,R],a_i\neq a_j \]那麼,我們得特別處理一下 \(d=0\),可以 \(\mathcal O(n)\) 掃一遍,不贅述。
對於 \(d>0\) 的情況,我們可以將一段取模 \(d\) 餘數相同的區間拿出來單獨處理。
現在,我們有了一段可能會出現答案的區間 \([L,R]\),並將它們拿出來單獨處理,我們假設將它們放到一個新的陣列 \(b\) 上,現在,我們考慮如何在這個陣列上得到合法的區間。我們可以先令 \(b_i\leftarrow \left\lfloor{b_i\over d}\right\rfloor\),因為我們不用擔心餘數問題了。
先搞清楚限制條件,對於 \(b\) 的一段區間 \([L,R]\),合法的條件是
\[\max-\min+1-(R-L+1)\le k\Rightarrow \max-\min+L-R\le k \]也就是我們需要得到一個區間的最大或最小值,但是還有 \(L,R\)
考慮依次移動右端點,維護每個左端點對應上述的值,那麼,現在右端點在我們手上,即右端點可以看做是 “固定” 的,對於每個左端點,我們需要維護 \([L,R]\) 的最大值和最小值,這個時候引入單調棧+線段樹!具體來說,維護單增單減的兩個棧,單增棧維護最小值,單減棧維護最大值,可以這樣看:
對於紅色的這個值,它負責的就是 \([i+1,j]\) 這個區間的最小值。
或者說我們為什麼要使用單調棧,其本質原因是隨著 \(L\) 的變小,它的 最小值/最大值 只會逐漸往極端走,不可能會 “迴轉”。
對於單減的棧原理也是一樣。
當我們移動 \(R\) 時,要將 \(b_R\) 加入這兩個棧,舉單增的棧(維護最小值)來說:當我們要將 \(i\)
而當我們移動 \(R\) 時,要注意將整個區間(不同於 \([1,R]\))都減去 \(1\),而將 \(R\) 這個單點加上 \(R\)(因為有 \(+L\) 這個項,而線段樹本質是在維護每個 \(L\) 對應的上述表示式的值,而 \(R\) 同時也是一個新的左端點)。
處理完這些之後,線上段樹上二分找到最小的 \(L\) 滿足上述表示式小於等於 \(k\) 即可。
但是需要處理一個問題 —— 出現重複的數,假設加入 \(b_R\),發現其重複出現,上一次出現的位置在 \(p\),那麼我們只需要令所有 \([1,p]\) 的線段樹上的值為 \(+\infty\) 即可 —— 這樣線段樹二分時這段區間必然不滿足其值小於等於 \(k\).
總的複雜度就是 \(\mathcal O(n\log n)\) 了。
叄、參考程式碼 ¶
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#include<map>
#include<stack>
using namespace std;
// #define NDEBUG
#include<cassert>
namespace Elaina{
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define mmset(a, b) memset(a, b, sizeof a)
// #define int long long
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>inline void getmin(T& x, const T rhs){ x=min(x, rhs); }
template<class T>inline void getmax(T& x, const T rhs){ x=max(x, rhs); }
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template<class T>inline void writc(T x, char s='\n'){
static int fwri_sta[1005], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
}
using namespace Elaina;
const int maxn=2e5;
const int inf=1e9+1;
int n, k, d;
int a[maxn+5];
inline void input(){
n=readin(1), k=readin(1), d=readin(1);
rep(i, 1, n) a[i]=readin(1)+inf;
}
namespace special{
inline int launch(){
int len=1, l=1, cur=1;
rep(i, 2, n){
if(a[i]==a[i-1]) ++cur;
else cur=1;
if(cur>len) len=cur, l=i-cur+1;
}
writc(l, ' '), writc(l+len-1);
return 0;
}
}
int len, l, s;
namespace saya{
ll tag[maxn<<2|2], mn[maxn<<2|2];
#define ls (i<<1)
#define rs (i<<1|1)
#define mid ((l+r)>>1)
#define _lq ls, l, mid
#define _rq rs, mid+1, r
void build(int i, int l, int r){
tag[i]=mn[i]=0;
if(l==r) return;
build(_lq), build(_rq);
}
inline void update(int i, ll x){ tag[i]+=x, mn[i]+=x; }
inline void pushup(int i){ mn[i]=min(mn[ls], mn[rs]); }
inline void pushdown(int i){
update(ls, tag[i]), update(rs, tag[i]);
tag[i]=0;
}
void modify(int i, int l, int r, int L, int R, ll x){
if(L<=l && r<=R) return update(i, x);
if(tag[i]) pushdown(i);
if(L<=mid) modify(_lq, L, R, x);
if(mid<R) modify(_rq, L, R, x);
pushup(i);
}
int query(int i, int l, int r, int k){
if(mn[i]>k) return -1;
if(l==r) return l;
if(tag[i]) pushdown(i);
if(mn[ls]<=k) return query(_lq, k);
return query(_rq, k);
}
void check(int i, int l, int r){
if(l==r) return printf("val[%d] == %lld\n", l, mn[i]), void();
if(tag[i]) pushdown(i);
check(_lq), check(_rq);
}
#undef ls
#undef rs
#undef mid
#undef _lq
#undef rq
}
map<int, int>pos;
stack<int>up, down;
int b[maxn+5];
inline void solve(int L, int R){
/** prelude */
s=R-L+1;
rep(i, L, R) b[i-L+1]=a[i]/d;
/** some clear */
saya::build(1, 1, s); pos.clear();
while(!up.empty()) up.pop();
while(!down.empty()) down.pop();
up.push(0), down.push(0);
/** enumerate the right endpos */
rep(i, 1, s){
saya::modify(1, 1, s, 1, s, -1); saya::modify(1, 1, s, i, i, i);
/** @p inf shoule be large enough, but it shouldn't to too large */
if(pos.count(b[i])) saya::modify(1, 1, s, 1, pos[b[i]], inf);
pos[b[i]]=i;
/** maintain the minimum value */
while(up.top() && b[up.top()]>b[i]){
int pre=up.top(); up.pop();
saya::modify(1, 1, s, up.top()+1, pre, b[pre]);
}
saya::modify(1, 1, s, up.top()+1, i, -b[i]);
up.push(i);
/** maintain the maximum value */
while(down.top() && b[down.top()]<b[i]){
int pre=down.top(); down.pop();
saya::modify(1, 1, s, down.top()+1, pre, -b[pre]);
}
saya::modify(1, 1, s, down.top()+1, i, b[i]);
down.push(i);
/** DEBUG */
// printf("When i == %d, the segment tre:>\n", i);
// saya::check(1, 1, s);
/** get the legal left endpos */
int ret=saya::query(1, 1, s, k);
// printf("When i == %d, ret == %d\n", i, ret);
if(ret!=-1 && i-ret+1>len) len=i-ret+1, l=L+ret-1;
// printf("When i == %d, l == %d, len == %d\n", i, l, len);
}
// printf("successfully quit!\n");
}
signed main(){
input();
if(d==0) return special::launch();
int lst=1;
rep(i, 2, n+1) if(a[i]%d!=a[lst]%d || i==n+1)
solve(lst, i-1), lst=i;
writc(l, ' '), writc(l+len-1, '\n');
return 0;
}
肆、關鍵之處 ¶
真不知道該怎麼說,但是這種移動右端點,維護左端點真該學一下,同時使用單調棧維護最大最小值。