【数据结构】你能凑出一个等差数列吗?
一个静态问题
找一个最长的子区间使得加入至多 \(k\) 个数以后,排序后是一个公差为 \(d\) 的等差数列。
多解输出 \(l\) 最小的。
\(1 \leq n \leq 2 \times 10^5,0 \leq k \leq 2 \times 10^5,0 \leq d \leq 10^9\) 。
考虑可以形成一个等差数列的条件:
-
\([l,r]\) 中所有数模 \(d\) 同余。
-
极差不超过 \((r - l + k)d\) 。
-
没有重复数字
考虑扫描线,从右至左扫描左端点,对于极差这个东西,可以用线段树和单调栈维护最大最小值。
记录一个 “可用的右端点” \(nr\) ,对于模 \(d\) 同余,记录 \(a_l\) 模 \(d\) 的余数,如果和 \(a_{l + 1}\) 不同,就
将 \(nr\) 直接改成 \(l\) 。对于没有重复数字这个条件,记录一个 \(set\) ,如果有重复数字就将 \(nr--\) 即可。
考虑极差不超过某个值,就是 \(max_r - min_r - rd \leq kd - ld\) 。这个更新 \(min\) 和 \(max\) 的时候顺便更新一下这个玩意的区间最小值,然后把 \(ld\) 带进去二分即可。
时间复杂度 \(\Theta(n \log n)\) 。
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5,M = 2e9;
typedef long long ll;
int n,k,d;
struct Seg_Tree{
ll a[N << 2],tag[N << 2];
inline void pushup(int pos) {a[pos] = max(a[pos << 1],a[pos << 1 | 1]);}
inline void pushdown(int pos)
{
a[pos << 1] += tag[pos];
a[pos << 1 | 1] += tag[pos];
tag[pos << 1] += tag[pos];
tag[pos << 1 | 1] += tag[pos];
tag[pos] = 0;
}
inline void build(int l,int r,int pos)
{
if(l == r) {a[pos] = 1ll * l * d + 1ll * k * d; return;}
int mid = (l + r) >> 1;
build(l,mid,pos << 1); build(mid + 1,r,pos << 1 | 1);
pushup(pos);
}
inline void modify(int l,int r,int L,int R,ll k,int pos)
{
if(L <= l && r <= R) {a[pos] += k; tag[pos] += k; return;}
int mid = (l + r) >> 1;
pushdown(pos);
if(L <= mid) modify(l,mid,L,R,k,pos << 1);
if(R > mid) modify(mid + 1,r,L,R,k,pos << 1 | 1);
pushup(pos);
}
inline int query(int l,int r,int L,int R,ll req,int pos)
{
if(!(L <= l && r <= R))
{
int mid = (l + r) >> 1;
pushdown(pos); pushup(pos);
int ret = 0;
if(L <= mid) ret = max(ret,query(l,mid,L,R,req,pos << 1));
if(R > mid) ret = max(ret,query(mid + 1,r,L,R,req,pos << 1 | 1));
return ret;
}
if(l == r) {return (a[pos] >= req) ? l : L;}
int mid = (l + r) >> 1;
pushdown(pos);
pushup(pos);
if(a[pos << 1 | 1] >= req) return query(mid + 1,r,L,R,req,pos << 1 | 1);
else return query(l,mid,L,R,req,pos << 1);
}
inline ll qval(int l,int r,int x,int pos)
{
if(l == r) return a[pos];
pushdown(pos); pushup(pos);
int mid = (l + r) >> 1;
if(x <= mid) return qval(l,mid,x,pos << 1);
else return qval(mid + 1,r,x,pos << 1 | 1);
}
}t;
int a[N];
inline void solve1()
{
int ansl = 1,ansr = 1;
for(int i = 1,j;i <= n;i++)
{
j = i;
while(j < n && a[j + 1] == a[i]) ++j;
if(j - i + 1 > ansr - ansl + 1) ansr = j,ansl = i;
}
cout<<ansl<<" "<<ansr;
}
struct Stack{
int s[N],top = 0;
inline int front() {return s[top];}
inline int scd() {return s[top - 1];}
inline void push(int x) {s[++top] = x;}
}mx,mn;
set <int> s;
int main()
{
cin>>n>>k>>d;
for(int i = 1;i <= n;i++) cin>>a[i],a[i] += 1e9;
if(!d) {solve1(); return 0;}
int ansl = n,ansr = n;
t.build(1,n,1); mx.s[0] = n + 1; mn.s[0] = n + 1;
for(int l = n,r = n,nr = n,mod = a[n] % d;l >= 1;l--)
{
int lst = l + 1;
while(mx.top > 0 && a[l] >= a[mx.front()])
{
t.modify(1,n,lst,mx.scd() - 1,a[mx.front()],1);
lst = mx.scd();
--mx.top;
}
mx.push(l);
t.modify(1,n,l,lst - 1,-a[l],1);
lst = l + 1;
while(mn.top > 0 && a[l] <= a[mn.front()])
{
t.modify(1,n,lst,mn.scd() - 1,-a[mn.front()],1);
lst = mn.scd();
--mn.top;
}
mn.push(l);
t.modify(1,n,l,lst - 1,a[l],1);
if(mod != a[l] % d) nr = l,s.clear(),mod = a[l] % d;
while(s.find(a[l]) != s.end()) s.erase(a[nr]),nr--;
s.insert(a[l]);
int ar = t.query(1,n,l,nr,1ll * l * d,1);
if(ar - l + 1 >= ansr - ansl + 1) ansr = ar,ansl = l;
}
cout<<ansl<<" "<<ansr;
return 0;
}
一个动态问题
给定长度为 \(n\) 的序列 \(a_i\) ,有 \(m\) 次操作,分为两种:
1 x y
:将 \(a_x\) 修改为 \(y\) 。
2 l r k
:询问区间 \([l,r]\) 的数能否恰好构成一个公差为 \(k\) 的等差数列。
\(1 \leq n,m \leq 3 \times 10^5,0 \leq a_i,y,k \leq 10^9\) 。
强制在线。
再来考虑充要条件:
-
没有相同的数
-
极差为 \((r - l)k\)
我们发现,这个时候你不能再通过模 \(k\) 的限制做了,因为 \(k\) 在变化。
考虑其他的条件,发现任意两项的差都是 \(k\) 的倍数。
于是有了这条限制:
- 定义差分序列 \(b_i = a_i - a_{i + 1}\) ,\(|\gcd_{i = l}^{r - 1}b_i| = k\) 。
我们发现,如果满足前两个条件,又满足第三个条件,那么这一定是一个等差数列,如果不满足第三个条件,就一定不是。
而且上文又提到等差数列一定满足条件 \(3\) ,所以条件 \(123\) 是充要的。
这样,对于相同的数,我们套路地维护 \(pre_i\) 表示 \(i\) 前面最近的等于 \(a_i\) 的位置,没有相同的数就是 \(\max_{i = l}^r pre_i < l\) 。
所以,外面用 \(set\) 维护所有数的出现位置,里面用线段树维护 \(max,min,pre,gcd\) 即可。
时间复杂度 \(\Theta(n \log n)\) 。
什么?你说 pushup 的时候暴力 gcd 复杂度 \(\log^2 n\) ?事实上不能将 \(\log\) 次 gcd 单纯地看成 \(\Theta(\log ^2n)\) 。考虑上一层要 gcd 的值一定基于当前层 gcd 的值,我们都知道辗转相除一次至少减半,所以可以看作 \(b_i\) 从底层向上和很多个数 gcd 到顶层,辗转相除次数不会超过 \(\Theta(\log n)\) 。
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5,inf = 2e9;
struct Segment_Tree{
int maxi[N << 2],mini[N << 2],g[N << 2],pre[N << 2];
inline void pushup1(int pos) {maxi[pos] = max(maxi[pos << 1],maxi[pos << 1 | 1]); mini[pos] = min(mini[pos << 1],mini[pos << 1 | 1]);}
inline void pushup2(int pos) {g[pos] = __gcd(g[pos << 1],g[pos << 1 | 1]);}
inline void pushup3(int pos) {pre[pos] = max(pre[pos << 1],pre[pos << 1 | 1]);}
inline void modify_num(int l,int r,int x,int k,int pos)
{
if(l == r) {maxi[pos] = mini[pos] = k; return;}
int mid = (l + r) >> 1;
if(x <= mid) modify_num(l,mid,x,k,pos << 1);
else modify_num(mid + 1,r,x,k,pos << 1 | 1);
pushup1(pos);
}
inline void modify_del(int l,int r,int x,int k,int pos)
{
if(l == r) {g[pos] = k; return;}
int mid = (l + r) >> 1;
if(x <= mid) modify_del(l,mid,x,k,pos << 1);
else modify_del(mid + 1,r,x,k,pos << 1 | 1);
pushup2(pos);
}
inline void modify_pre(int l,int r,int x,int k,int pos)
{
if(l == r) {pre[pos] = k; return;}
int mid = (l + r) >> 1;
if(x <= mid) modify_pre(l,mid,x,k,pos << 1);
else modify_pre(mid + 1,r,x,k,pos << 1 | 1);
pushup3(pos);
}
inline pair <int,int> query_num(int l,int r,int L,int R,int pos)
{
if(L <= l && r <= R) return make_pair(maxi[pos],mini[pos]);
int mid = (l + r) >> 1; pair <int,int> now,ret = make_pair(-inf,inf);
if(L <= mid) now = query_num(l,mid,L,R,pos << 1),ret.first = max(ret.first,now.first),ret.second = min(ret.second,now.second);
if(R > mid) now = query_num(mid + 1,r,L,R,pos << 1 | 1),ret.first = max(ret.first,now.first),ret.second = min(ret.second,now.second);
return ret;
}
inline int query_del(int l,int r,int L,int R,int pos)
{
if(L <= l && r <= R) return g[pos];
int mid = (l + r) >> 1,ret = 0;
if(L <= mid) ret = __gcd(ret,query_del(l,mid,L,R,pos << 1));
if(R > mid) ret = __gcd(ret,query_del(mid + 1,r,L,R,pos << 1 | 1));
return ret;
}
inline int query_pre(int l,int r,int L,int R,int pos)
{
if(L <= l && r <= R) return pre[pos];
int mid = (l + r) >> 1,ret = 0;
if(L <= mid) ret = max(ret,query_pre(l,mid,L,R,pos << 1));
if(R > mid) ret = max(ret,query_pre(mid + 1,r,L,R,pos << 1 | 1));
return ret;
}
}t;
map <int,int> mp;
set <int> s[N * 2];
int n,m,a[N],cnt = 0;
int main()
{
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin>>n>>m;
for(int i = 1;i <= n;i++) cin>>a[i];
for(int i = 1;i <= n;i++)
{
if(mp.find(a[i]) == mp.end()) mp[a[i]] = ++cnt;
if(s[mp[a[i]]].size()) t.modify_pre(1,n,i,*s[mp[a[i]]].rbegin(),1);
s[mp[a[i]]].insert(i);
}
for(int i = 1;i <= n - 1;i++) t.modify_del(1,n,i,a[i] - a[i + 1],1);
for(int i = 1;i <= n;i++) t.modify_num(1,n,i,a[i],1);
for(int i = 1,op,x,y,k,yesnum = 0;i <= m;i++)
{
cin>>op>>x>>y;
x ^= yesnum; y ^= yesnum;
if(op == 1)
{
int ori = mp[a[x]],pr = 0,sf = 0;
s[ori].erase(x);
set <int> :: iterator it = s[ori].lower_bound(x);
if(it != s[ori].begin())
{
it--;
pr = *it;
it++;
}
if(it != s[ori].end()) sf = *it;
if(sf) t.modify_pre(1,n,sf,pr,1);
s[ori].erase(x);
a[x] = y;
if(mp.find(a[x]) == mp.end()) mp[a[x]] = ++cnt;
ori = mp[a[x]]; pr = 0; sf = 0;
s[ori].insert(x);
it = s[ori].find(x);
if(it != s[ori].begin())
{
it--;
pr = *it;
it++;
}
it++;
if(it != s[ori].end()) sf = *it;
t.modify_pre(1,n,x,pr,1);
if(sf) t.modify_pre(1,n,sf,x,1);
t.modify_del(1,n,x,a[x] - a[x + 1],1);
if(x > 1) t.modify_del(1,n,x - 1,a[x - 1] - a[x],1);
t.modify_num(1,n,x,a[x],1);
}
else
{
cin>>k;
k ^= yesnum;
pair <int,int> num = t.query_num(1,n,x,y,1);
if(k == 0)
{
if(num.first == num.second) puts("Yes"),++yesnum;
else puts("No");
continue;
}
if(x == y) {puts("Yes"),++yesnum; continue;}
if(num.first - num.second == 1ll * (y - x) * k && abs(t.query_del(1,n,x,y - 1,1)) == k && t.query_pre(1,n,x,y,1) < x) puts("Yes"),++yesnum;
else puts("No");
}
}
return 0;
}