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,因此,時間複雜度爲 級別。
- 注意到並不會修改ai,因此,可以考慮RMQ。可以用ST算法 預處理一下,然後對於每個區間, 找出最大值的位置m。
- 若m-l<r-m,我們可以暴枚左端點,再在右邊查詢合法的右端點的數量;否則枚舉右端點,然後再左邊查詢合法的左端點的數量。
- 分析一下時間複雜度。若一個位置被枚舉到了一次,則它所在的區間至少會縮減成原來的 ,因此一個位置最多被枚舉 次。
- 因此,暴枚左/右端點的複雜度爲 。
- 先推一波式子。若區間[x,y]合法,當且僅當: 。
- 若我們枚舉了一個左端點x,則合法的y必須滿足:
- 若我們枚舉了y,則x須滿足:
- 於是,我們得到了 個形如“x在區間[l,r]中出現了幾次”的詢問。
於是這道題變成了主席樹模板題。可以離線處理。
- 對於每個形如“x在區間[l,r]中出現了幾次”的詢問,我們可以將其插到第r、第l-1個位置。
- 我們順序掃一遍,維護一個桶cnt。若當前我們掃到i,則cnt[x]表示x在區間[1,i]出現的次數。
- 於是,我們掃到第l-1個位置時,查詢一下,此時貢獻爲負;掃到第r個位置時,查詢一下,此時貢獻爲正。
時間複雜度: 。
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);
}