[CF1422F] Boring Queries
前言
線段樹練習賽 T1,最後一分鐘調出來了。
所以我T2爆零。
題目
洛谷有翻譯。
講解
首先強制線上已經完全抑制了我們的想象力,而答案太大需要取模,我們只能思考對每個數質因數分解,然後求指數最大值的做法。
對於每個質因數我們都維護一棵線段樹顯然不現實,因為 \(200000\) 以內的質數已經多達 \(17984\) 個。
根據套路,我們將值域分塊,\(\sqrt{200000}\approx 447\), \(447\) 以內的質數只有 \(86\) 個。
對於這 \(86\) 個數,我們都開一棵線段樹(或者一棵線段樹維護 \(86\) 個值),然後詢問的時候查詢區間最大值即可。
這個部分的時間複雜度為 \(O(86n\log_2)\)
為了卡常,我還預處理了這 \(86\) 個數的冪,因為指數顯然不會很大,所以這個冪可以預處理。
排除掉 \(447\) 以內的數之後,每個數只會存在至多一個大於 \(447\) 的質因數,這裡有兩個做法。
做法1
這是考試的時候的做法,時間複雜度不優,但是空間更優。
考慮分塊。
對整個序列分塊,預處理整塊之間的最小公倍數,當然這裡的序列已經不含 \(447\) 以內的因數了。
對於散塊我們可以使用求區間不同數的經典套路:如果一個數的位置在詢問區間中,而且前面與它相同的數的位置在詢問區間外,那麼才計入貢獻。
由於散塊在整塊的前面和後面都有,所以要預處理前驅和後繼,然後暴力判斷即可。
時間複雜度為 \(O((n+q)\sqrt{n})\),空間為 \(O(n)\)。
做法2
同樣是做法1中的經典套路,對於每個數我們記錄 \((pre_i,i)\) 這樣一組資訊,放到線段樹的葉子上,然後向上按 \(pre_i\) 單增進行歸併排序。
每次詢問時二分割槽間中最大的一個在詢問區間左邊的前驅,字首積即為答案,所以歸併的時候還要記錄字首積。
時間複雜度 \(O(n\log^2_2n)\),空間為 \(O(n\log_2n)\)。
當然用主席樹也行。
程式碼
考試程式碼。
//12252024832524 #include <map> #include <cmath> #include <cstdio> #include <cstring> #include <algorithm> #define TT template<typename T> using namespace std; typedef long long LL; const int MAXN = 200005; const int MAXB = 450;//其實這裡應該是sqrt(n),大概是320 const int MOD = 1000000007; int n,Q,tp; int a[MAXN]; LL Read() { LL x = 0,f = 1;char c = getchar(); while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();} while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();} return x * f; } TT void Put1(T x) { if(x > 9) Put1(x/10); putchar(x%10^48); } TT void Put(T x,char c = -1) { if(x < 0) putchar('-'),x = -x; Put1(x); if(c >= 0) putchar(c); } TT T Max(T x,T y){return x > y ? x : y;} TT T Min(T x,T y){return x < y ? x : y;} TT T Abs(T x){return x < 0 ? -x : x;} bool vis[MAXN]; int prime[MAXN],pn = -1,inv[MAXN]; void sieve(int x) { for(int i = 2;i <= x;++ i) { if(!vis[i]) prime[++pn] = i; for(int j = 0;j <= pn && i * prime[j] <= x;++ j) { vis[i * prime[j]] = 1; if(i % prime[j] == 0) break; } } inv[0] = inv[1] = 1; for(int i = 2;i <= x;++ i) inv[i] = 1ll * (MOD - MOD/i) * inv[MOD%i] % MOD; } int MAX[MAXN << 2][86],ri[86]; #define lc (x<<1) #define rc (x<<1|1) void Build(int x,int l,int r) { if(l == r) { a[l] = Read(); for(int i = 0;i < 86;++ i) while(a[l] % prime[i] == 0) ++MAX[x][i],a[l] /= prime[i]; return; } int mid = (l+r) >> 1; Build(lc,l,mid); Build(rc,mid+1,r); for(int i = 0;i < 86;++ i) MAX[x][i] = Max(MAX[lc][i],MAX[rc][i]); } void QQ(int x,int l,int r,int ql,int qr) { if(ql <= l && r <= qr) { for(int i = 0;i < 86;++ i) ri[i] = Max(ri[i],MAX[x][i]); return; } int mid = (l+r) >> 1; if(ql <= mid) QQ(lc,l,mid,ql,qr); if(mid+1 <= qr) QQ(rc,mid+1,r,ql,qr); } map<int,int> m; int mi[86][30]; int qpow(int x,int y) { int ret = 1; while(y){if(y & 1) ret = 1ll * ret * x % MOD;x = 1ll * x * x % MOD;y >>= 1;} return ret; } int lst; int pre[MAXN],suf[MAXN]; int lcm[MAXB][MAXB]; bool ex[MAXN]; void solve2() { for(int i = 0;i < 86;++ i) { mi[i][0] = 1; for(int j = 1;j < 30;++ j) mi[i][j] = 1ll * mi[i][j-1] * prime[i] % MOD; } Build(1,0,n-1); m[1] = -1; for(int i = 0;i <= pn;++ i) m[prime[i]] = -1; for(int i = 0;i < n;++ i) pre[i] = m[a[i]],m[a[i]] = i; m.clear(); m[1] = n; for(int i = 0;i <= pn;++ i) m[prime[i]] = n; for(int i = n-1;i >= 0;-- i) suf[i] = m[a[i]],m[a[i]] = i; int B = ceil(sqrt(n)); for(int i = 0;i*B < n;++ i) { int l = i*B,cao = 1; for(int j = i;j*B < n;++ j) { int r = Min(n-1,(j+1)*B-1); for(int k = l;k <= r;++ k) if(!ex[a[k]]) ex[a[k]] = 1,cao = 1ll * cao * a[k] % MOD; lcm[i][j] = cao; l = r+1; } for(int j = 0;j <= pn;++ j) ex[prime[j]] = 0; } Q = Read(); for(int i = 1;i <= Q;++ i) { int l = (lst + Read()) % n,r = (lst + Read()) % n; if(l > r) swap(l,r); lst = 1; for(int j = 0;j < 86;++ j) ri[j] = 0; QQ(1,0,n-1,l,r); for(int j = 0;j < 86;++ j) lst = 1ll * lst * mi[j][ri[j]] % MOD; int LID = l/B,RID = r/B; if(LID+1 >= RID) { for(int j = l;j <= r;++ j) if(a[j] > 1 && pre[j] < l) lst = 1ll * lst * a[j] % MOD; } else { lst = 1ll * lst * lcm[LID+1][RID-1] % MOD; int ll = l,rr = (LID+1)*B-1,xx = RID*B; for(int j = ll;j <= rr;++ j) if(a[j] > 1 && suf[j] >= xx) lst = 1ll * lst * a[j] % MOD; ll = RID*B; rr = r; for(int j = ll;j <= rr;++ j) if(a[j] > 1 && pre[j] < l) lst = 1ll * lst * a[j] % MOD; } Put(lst,'\n'); } } int main() { // freopen("lcm.in","r",stdin); // freopen("lcm.out","w",stdout); sieve(200000); n = Read(); solve2(); return 0; }
花絮
程式碼中的 solve2
函式是線上演算法,solve1
是離線演算法,只不過被我刪掉了(考試的時候離線有60pts)。
也許我不打離線做法就有時間做T2了。