【JZOJ5807】【NOIP提高A組模擬2018.8.13】簡單的區間(分治+RMQ+二維偏序問題)

Problem

這裏寫圖片描述

Hint

對於 30% 的數據,n ≤ 3000;
對於另外 20% 的數據,數列 a 爲隨機生成;
對於 100% 的數據,1 ≤ n ≤ 3 × 10^5 , 1 ≤ k ≤ 10^6 , 1 ≤ ai ≤ 10^9。

Solution

  • 考慮分治。對於區間[l,r],我們找出其中最大值的位置m,則可以計算出左端點在[l,m],右端點在[m,r]的合法區間數量,然後分治處理子區間[l,m-1],[m+1,r]。
  • 對於找最大值,我們顯然不能暴力枚。因爲如果最大值一直在l(在r同理),則分治時一直將l++,那麼區間長度即爲n~1,因此,時間複雜度爲O(n2) 級別。
  • 注意到並不會修改ai,因此,可以考慮RMQ。可以用ST算法O(nlog2n) 預處理一下,然後對於每個區間,O(1) 找出最大值的位置m。

  • 若m-l<r-m,我們可以暴枚左端點,再在右邊查詢合法的右端點的數量;否則枚舉右端點,然後再左邊查詢合法的左端點的數量。
  • 分析一下時間複雜度。若一個位置被枚舉到了一次,則它所在的區間至少會縮減成原來的12 ,因此一個位置最多被枚舉O(log2n) 次。
  • 因此,暴枚左/右端點的複雜度爲O(nlog2n)

  • 先推一波式子。若區間[x,y]合法,當且僅當:sum[y]sum[x1]am0(mod k)
  • 若我們枚舉了一個左端點x,則合法的y必須滿足:
    myrsum[y]sum[x1]+am(mod k)
  • 若我們枚舉了y,則x須滿足:
    lxmsum[x1]=sum[y]am(mod k)
  • 於是,我們得到了O(nlog2n) 個形如“x在區間[l,r]中出現了幾次”的詢問。於是這道題變成了主席樹模板題。可以離線處理。

  • 對於每個形如“x在區間[l,r]中出現了幾次”的詢問,我們可以將其插到第r、第l-1個位置。
  • 我們順序掃一遍,維護一個桶cnt。若當前我們掃到i,則cnt[x]表示x在區間[1,i]出現的次數。
  • 於是,我們掃到第l-1個位置時,查詢一下,此時貢獻爲負;掃到第r個位置時,查詢一下,此時貢獻爲正。

時間複雜度:O(nlog2n)

Code

#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define rep(i,x) for(query *i=x; i; i=i->ne)
using namespace std;
typedef long long ll;

const int N=6e5+1,inf=0x7FFFFFFF;
int i,j,n,k,a[N],sum[N],f[N][19],x,y,cnt[N<<1];
ll ans;
struct query
{
    int x,v;
    query *ne;
    query(int x,int v,query *ne):x(x),v(v),ne(ne){}
}*fin[N];

inline void newquery(int l,int r,int x)
{
         fin[r  ]=new query(x, 1,fin[r  ]);
    if(l)fin[l-1]=new query(x,-1,fin[l-1]);
}

void solve(int l,int r)
{
    if(l>r) return;
    int l2=log2(r-l+1); 
    x=f[l][l2]; y=f[r-(1<<l2)+1][l2];
    int m=( a[x]>a[y] ? x : y );

    if(m-l<r-m)
            fo(i,l,m) newquery(m,r,(sum[i-1]+a[m])%k);
    else    fo(i,m,r) newquery(l-1,m-1,(sum[i]-a[m]%k+k)%k);

    solve(l,m-1); solve(m+1,r);
}

int main()
{
    freopen("interval.in","r",stdin);
    freopen("interval.out","w",stdout);
    scanf("%d%d",&n,&k);
    fo(i,1,n) scanf("%d",&a[i]), sum[i]=(sum[i-1]+a[i])%k, f[i][0]=i;

    fo(j,1,18)
        fo(i,1,n) 
        {
            if(!f[x=i+(1<<j-1)][j-1]) break;
            f[i][j]=( a[f[i][j-1]]>a[f[x][j-1]] ? f[i][j-1] : f[x][j-1]);
        }

    solve(1,n);

    fo(x,0,n)
    {
        cnt[sum[x]]++;
        rep(i,fin[x]) 
        ans+=1ll*cnt[i->x]*i->v;
    }
    printf("%lld",ans-n);
}
發佈了114 篇原創文章 · 獲贊 52 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章