HDU - 5381 The sum of gcd(莫队/线段树区间合并)
題目鏈接:點擊查看
題目大意:給出一個長度為 nnn 的序列,再給出 mmm 次詢問,每次詢問需要回答區間 [L,R][L,R][L,R] 內所有子區間的 gcdgcdgcd 之和。更具體的,對于詢問 [L,R][L,R][L,R],輸出 ∑l=LR∑r=lRgcd?{al,al+1,?,ar}\sum\limits_{l=L}^{R}\sum\limits_{r=l}^{R}\gcd\{a_l,a_{l+1},\cdots,a_r\}l=L∑R?r=l∑R?gcd{al?,al+1?,?,ar?}
題目分析:首先需要知道 gcdgcdgcd 的一個性質,因為 gcdgcdgcd 運算每次至少除以二,所以一個連續的區間內,最多有 lognlognlogn 個不同的 gcdgcdgcd 取值。也就是說一個連續的區間可以被分成 lognlognlogn 個連續的子區間。
本題離線的話是可以直接掛莫隊的,只需要預處理一下每個前綴和后綴的所有 gcdgcdgcd 的不同取值,在擴展區間和收縮區間時就可以將 O(n)O(n)O(n) 級別的子區間壓縮成 O(logn)O(logn)O(logn) 個子區間了,時間復雜度是 O(nlognn)O(nlogn\sqrt{n})O(nlognn?)
全網幾乎都是莫隊離線寫的,但我個人感覺用線段樹在線也是可以做的,具體難點就是在線段樹的區間合并上,考慮如何將左區間和右區間進行合并。還是需要用到上面的性質,可以將左右區間分別拆成 lognlognlogn 個連續的區間,然后 O(log2n)O(log^2n)O(log2n) 去暴力合并即可,總的復雜度是 O(nlog3n)O(nlog^3n)O(nlog3n)
代碼:
線段樹
莫隊
// Problem: The sum of gcd // Contest: Virtual Judge - HDU // URL: https://vjudge.net/problem/HDU-5381 // Memory Limit: 65 MB // Time Limit: 1000 ms // // Powered by CP Editor (https://cpeditor.org)// #pragma GCC optimize(2) // #pragma GCC optimize("Ofast","inline","-ffast-math") // #pragma GCC target("avx,sse2,sse3,sse4,mmx") #include<iostream> #include<cstdio> #include<string> #include<ctime> #include<cmath> #include<cstring> #include<algorithm> #include<stack> #include<climits> #include<queue> #include<map> #include<set> #include<sstream> #include<cassert> #include<bitset> #include<list> #include<unordered_map> #define lowbit(x) (x&-x) using namespace std; typedef long long LL; typedef unsigned long long ull; template<typename T> inline void read(T &x) {T f=1;x=0;char ch=getchar();while(0==isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}while(0!=isdigit(ch)) x=(x<<1)+(x<<3)+ch-'0',ch=getchar();x*=f; } template<typename T> inline void write(T x) {if(x<0){x=~(x-1);putchar('-');}if(x>9)write(x/10);putchar(x%10+'0'); } const int inf=0x3f3f3f3f; const int N=1e4+100; int size,n,m,a[N],st[N][20],Log[N]; LL ans[N]; vector<int>L[N],R[N]; struct query {int l,r,id;bool operator<(const query& a)const{if(l/size!=a.l/size)return l<a.l;else if((l/size)&1)return r<a.r;elsereturn r>a.r;} }q[N]; void get_st() {for(int i=1;i<=n;i++) Log[i]=log2(i);for(int i=1;i<=n;i++) st[i][0]=a[i];for(int i=1;i<=19;i++) for(int j=1;j+(1<<i)-1<=n;j++) st[j][i]=__gcd(st[j][i-1],st[j+(1<<(i-1))][i-1]); } int ask(int l,int r) {int k=Log[r-l+1];return __gcd(st[l][k],st[r-(1<<k)+1][k]); } int get_L(int l,int r,int R,int val) {//[l,r]找到gcd[i:R]==val的位置int ans=-1;while(l<=r) {int mid=(l+r)>>1;if(ask(mid,R)==val) r=mid-1,ans=mid;else l=mid+1;}return ans; } int get_R(int l,int r,int L,int val) {//[l,r]找到gcd[L:i]==val的位置int ans=-1;while(l<=r) {int mid=(l+r)>>1;if(ask(L,mid)==val) l=mid+1,ans=mid;else r=mid-1;}return ans; } LL left(int l,int r) {LL ans=0;for(int i=1;i<(int)R[l].size();i++) {ans+=1LL*(min(R[l][i],r)-R[l][i-1])*ask(l,R[l][i]);if(R[l][i]>r) {break;}}return ans; } LL right(int l,int r) {LL ans=0;for(int i=1;i<(int)L[r].size();i++) {ans+=1LL*(L[r][i-1]-max(L[r][i],l))*ask(L[r][i],r);if(L[r][i]<l) {break;}}return ans; } void solve() {int l=1,r=0;LL sum=0;for(int i=1;i<=m;i++){int ql=q[i].l;int qr=q[i].r;while(l>ql) sum+=left(l-1,r),l--;while(r<qr) sum+=right(l,r+1),r++;while(l<ql) sum-=left(l,r),l++;while(r>qr) sum-=right(l,r),r--;ans[q[i].id]=sum;} } void init() {get_st();for(int i=1;i<=n;i++) {L[i].clear();L[i].push_back(i+1);int pos=i;while(pos>=1) {pos=get_L(1,pos,i,ask(pos,i));L[i].push_back(pos);pos--;}}for(int i=1;i<=n;i++) {R[i].clear();R[i].push_back(i-1);int pos=i;while(pos<=n) {pos=get_R(pos,n,i,ask(i,pos));R[i].push_back(pos);pos++;}} } int main() { #ifndef ONLINE_JUDGE // freopen("data.in.txt","r",stdin); // freopen("data.out.txt","w",stdout); #endif // ios::sync_with_stdio(false);size=100;int w;cin>>w;while(w--) {scanf("%d",&n);for(int i=1;i<=n;i++) {scanf("%d",a+i);}init();scanf("%d",&m);for(int i=1;i<=m;i++) {scanf("%d%d",&q[i].l,&q[i].r);q[i].id=i;ans[i]=0;}sort(q+1,q+1+m);solve();for(int i=1;i<=m;i++) {printf("%lld\n",ans[i]);}}return 0; }總結
以上是生活随笔為你收集整理的HDU - 5381 The sum of gcd(莫队/线段树区间合并)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CodeForces - 1561E B
- 下一篇: CodeForces - 468C Ha