GYM 102439 Equal Mod Segments

題意是,給你一個序列,問有多少區間 [l,r] 滿足 a[l]%a[l+1]%a[l+2].....%a[r]==a[r]%a[r-1]%a[r-2]...%a[l]?

我們考慮一個區間 [l,r] 假設區間內最小值在pos處取得,那麼區間的合法性可以表示爲

a[l]%a[l+1]%a[l+2].....%a[pos]==a[r]%a[r-1]%a[r-2]...%a[pos].因爲在mod a[pos]之後,值是小於apos的,繼續取餘比較大的數也沒有什麼意義。

如果pos==l || pos==r 呢?如果區間內沒有等於apos的數,那麼區間一定是不合法的。只有區間內有多個等於apos的數纔有可能合法,這裏需要特判一下。

所以只要統計一個每個最小值左右相等的數的乘積,注意不要計算重複。

可以分治解決這個問題。我們每次找到區間內最小值(多個取最左邊的),遞歸左區間,遞歸右區間。

 

對於每一個分治的區間L,R,我們用set去維護所有數向左取模和所有數向右取模。

那麼計算的時候,先分別把左區間向右的mod apos, 右區間向左的mod pos,這裏注意,取模的時候先二分找到第一個大於apos的值,再向後依次取模,這樣保證每次操作都會使一個數變小。(這裏的複雜度是nlognlogn的,因爲一個最多有效取模log次,那麼n個數最多取模nlog次,再算上set的複雜度)。

然後統計答案,合併左右區間。

爲了保證分治的複雜度,就是要啓發式分治嘛。

統計答案的時候要遍歷小的區間,二分大的區間。合併的時候也是把小區間插入到大的區間裏去。

這樣複雜度也是nloglog的嘛。

 

#include <bits/stdc++.h>

using namespace std;

#define N 200025
#define M 610
#define ll long long
#define mod 1000000007
#define go(i,a,b) for(int i=(a);i<=(b);i++)
#define dep(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
#define inf 0x3f3f3f3f
#define ld long double
#define pii pair<int,int>
#define pdd pair<double,double>
#define vi vector<int>
#define madd(a,b) (a+=(b)%mod)%=mod
#define lowb(c,len,x) lower_bound(c+1,c+len+1,x)-c
#define uppb(c,len,x) upper_bound(c+1,c+len+1,x)-c
#define ls i*2+1
#define rs i*2+2
//#define mid (l+r)/2
#define lson l,mid,ls
#define rson mid+1,r,rs
//#define root 1,cnt,0
#define ms(a,b) memset(a,b,sizeof a)
#define muti int T,cas=1;cin>>T;while(T--)
#define lll __int128
#define si short int
#define fi first
#define se second
#define l(x) (x&-x)
#define G(x) for( int ii=h[x];ii;ii=eg[ii].n )

int mm[2*N],n,las[N];
int dp[2*N][20],a[N],cnt;
set<pii>st[N];
set<pii>::iterator it,it1,itt;
ll ans=0;
void init()
{
    mm[0]=-1;
    go(i,1,n){
        dp[i][0]=i;
        mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1];
    }
    go(j,1,mm[n])
    for(int i=1; i+(1<<j)-1<=n; i++)
        dp[i][j]=a[dp[i][j-1]]<=a[dp[i+(1<<(j-1))][j-1]]?dp[i][j-1]:dp[i+(1<<(j-1))][j-1];
}
int quy(int x,int y){
    if(x>y)return n+1;
    int k=mm[y-x+1];
    return a[dp[x][k]]<=a[dp[y-(1<<k)+1][k]]?dp[x][k]:dp[y-(1<<k)+1][k];
}
int join(int x,int y,int z){ //合併兩個set,插入a[mid]
    if(st[x].size()>st[y].size())swap(x,y);
   //if(!st[x].size())return y;
    for(it=st[x].begin();it!=st[x].end();it++){
        it1=st[y].lower_bound(pii(it->fi,0));
        if(it1==st[y].end()||it1->fi!=it->fi)st[y].insert(*it);
        else {
            pii tmp=pii(it1->fi,it1->se+it->se);
            st[y].erase(it1);
            st[y].insert(tmp);
        }
    }
    st[x].clear();
    it=st[y].lower_bound(pii(z,0));
    if(it!=st[y].end()&&it->fi==z){
        pii tmp=pii(it->fi,it->se+1);
        st[y].erase(it);
        st[y].insert(tmp);
    }
    else st[y].insert(pii(z,1));
    return y;
}
void cal(int x,int y){ //統計答案
    if(st[x].size()>st[y].size())swap(x,y);
    if(!st[x].size())return ;
    for(it=st[x].begin();it!=st[x].end();it++){
        it1=st[y].lower_bound(pii(it->fi,0));
        if(it1!=st[y].end()&&it1->fi==it->fi)ans+=1ll*(it->se)*(it1->se);
    }
}
//void pri(int x){
//    cout<<":::::::::::::"<<endl;
//    for(it=st[x].begin();it!=st[x].end();it++){
//        cout<<it->fi<<'-'<<it->se<<' ';
//    }
//    cout<<endl<<endl;
//}
void upd(int x,int y){ //對set所有的值 mod a[mid]
    for(it=st[x].lower_bound(pii(y,0));it!=st[x].end();){
        pii tmp=pii(it->fi%y,it->se);
        it1=it;it++;
        st[x].erase(it1);
        itt=st[x].lower_bound(pii(tmp.fi,0));
        if(itt!=st[x].end()&&itt->fi==tmp.fi){
            pii tmp1=pii(itt->fi,itt->se+tmp.se);
            st[x].erase(itt);
            st[x].insert(tmp1);
        }
        else st[x].insert(tmp);
    }
}

pii solve(int l,int r,int fl=0){ 
    if(fl)ans++;
    if(l>=r){
        if(l>r)return pii(++cnt,++cnt);
        //cout<<fl<<endl;
        st[++cnt].insert(pii(a[l],1));
        st[++cnt].insert(pii(a[l],1));
        return pii(cnt-1,cnt);
    }
    int mid=quy(l,r);
    //遞歸左右區間
    pii pl=solve(l,mid-1),pr=solve(mid+1,r,a[quy(mid+1,r)]==a[mid]?1:0),res;
    //分別取模a[mid]
    upd(pr.fi,a[mid]);upd(pl.se,a[mid]);
    //特判區間內有多個相同的最小值
    it=st[pr.fi].lower_bound(pii(0,0));
    if(it!=st[pr.fi].end()&&it->fi==0)ans+=fl*(it->se);
    //統計答案
    cal(pl.se,pr.fi);
    //合併左右區間,a[mid]
    res=pii(join(pl.fi,pr.fi,a[mid]),join(pl.se,pr.se,a[quy(mid+1,r)]==a[mid]?0:a[mid]));

    return res;
}

int main()
{
    cin>>n;
    go(i,1,n)scanf("%d",&a[i]);
    a[n+1]=inf;
    init();
    solve(1,n);

    cout<<ans+n<<endl;
}
/*
10
10 9 8 7 1 2 3 4 5 6
*/

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章