1. 程式人生 > 其它 >Codeforces Round 769 (Div. 2) D. New Year Concert

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\)

,我們對他前面的每一個位置 \(j(1\leq j\leq i)\) 都判斷一下,看下 \(a_j\)\(a_i\) 的區間 \(\gcd\) 是否等於區間長度,如果每個都不等,則不需要修改;否則,至少要修改一個數,修改誰呢?修改為啥呢?設我們要修改的數為 \(a_k\),注意到可以修改為任意正整數,顯然我們只需要將其修改成一個特別大的質數,就可以在保證 \([1,i]\) 區間合法性的同時,讓處在後面的區間 \([k+1,n]\) 中的每個位置 \(i'\) 只需判斷 \([k+1,i']\) 即可,所以為了保證修改次數為最小,\(k\) 一定要取最大值,因此 \(k=i\),只需修改 \(a_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 << " ";
	}
}