Codeforces Round 769 (Div. 2) D. New Year Concert
題面看這裡
題目大意
給你一個只含正整數的陣列 \(a\),你可以任意修改其中的元素為任意正整數,問你對於 \(a\) 的每個字首陣列\((a_1,a_1a_2,a_1a_2a_3\dots)\),至少需要修改多少個元素,才能滿足:該字首陣列中不存在任何一個子區間 \(a_l,a_{l+1}\dots a_{r-1},a_r\),滿足 \(\gcd(a_l,a_{l+1}\dots a_{r-1},a_r)=r-l+1\),即每個子區間的區間 \(\gcd\) 都不等於區間長度。
\((1\leq n\leq 2e5,~1\leq a_i\leq1e9)\)
題目分析
首先考慮最暴力的演算法,從左到右,對於每個位置 \(i\)
區間 \(\gcd\) 可以用 st表
或者 線段樹
\(n\log n\) 預處理出來,然後每次查詢區間 \(\gcd\) 的時間複雜度是 \(\log n\) 總複雜度 \(O(n^2\log n)\),虛擬碼如下:
int last=0,ans=0; //last記錄的是上一次修改的位置,j只需要遍歷[last+1,i]即可,小優化 for(int i=1;i<=n;i++){ bool flag=true; for(int j=i;j>last;j--){ if(query(j,i)==i-j+1){ flag=false; break; } } if(!flag) ans++,last=i; cout << ans << " "; }
接下來考慮優化,注意到重點是對於每一個非法的區間,我們都要修改其右端點的數,顯然,對於一個左端點 \(l_x\),最多隻能有一個右端點跟它構成非法區間。(左端點固定的區間 \(\gcd\) 是非遞增的,而區間長度又是遞增的,假如這個非法區間為 \([l_x,r_x]\),那麼 \(r_x\) 之後的區間 \(\gcd\) 只會不變或者更小,而區間長度一定會變大,所以一定不相等)
因此,對於每個位置 \(i\),我們需要找到以它為左端點的非法區間的右端點,記為 \(v_i\),如果不存在則令 \(v_i=0\)。那麼我們就可以 \(O(n)\) 求出答案:
long long nex=inf; //在多個非法區間中選出最小的右端點,即為下一次要修改的位置
for(int i=1;i<=n;i++){
if(v[i]){
nex=min(nex,v[i]);
}
if(i==nex){
ans++;
nex=inf;
}
cout << ans << " ";
}
現在問題轉化為了找到每個非法區間,通過上面的分析,發現 \(\gcd([l_x,r_x])\geq r_x-l_x+1\) 這個式子具有單調性,因此我們可以固定 \(l_x\),然後二分查詢最大的 \(r_x\) 滿足 \(\gcd([l_x,r_x])\geq r_x-l_x+1\),然後再判斷一下是否相等,相等就找到了一個非法區間。時間複雜度 \(O(n\log n\log n)\))。
程式碼實現
#include<bits/stdc++.h>
using namespace std;
constexpr long long inf=LLONG_MAX/10;
constexpr int maxn=2e5+6;
long long n,m,k,ans,_=1,a[maxn],v[maxn];
long long f[maxn][22];
long long lg[maxn];
long long gcd(long long a,long long b){
if(!a||!b) return a+b;
while((a%=b)&&(b%=a)) ;
return a+b;
}
void init(){
lg[0]=-1;
for(int i=1;i<=n;i++){
f[i][0]=a[i],lg[i]=lg[i>>1]+1;
}
lg[0]=0;
for(int j=1;j<=lg[n];j++){
for(int i=1;i+(1<<j)<n+2;i++){
f[i][j]=gcd(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
}
}
long long query(int l,int r){
int len=lg[r-l+1];
return gcd(f[l][len],f[r-(1<<len)+1][len]);
}
int main(){
cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i];
}
init();
for(int i=1;i<=n;i++){
int l=i,r=n,mid,rr;
while(l<=r){
mid=l+r>>1;
long long d=query(i,mid);
if(d>=mid-i+1){
rr=mid;
l=mid+1;
}
else {
r=mid-1;
}
}
if(query(i,rr)==rr-i+1){
v[i]=rr;
}
}
long long nex=inf;
for(int i=1;i<=n;i++){
if(v[i]){
nex=min(nex,v[i]);
}
if(i==nex){
ans++;
nex=inf;
}
cout << ans << " ";
}
}