非常好的转换
想了很久,想到了要对缝进行讨论,算每个缝的贡献,但是还是没想到最后一步。
-
可以发现,对长为一的区间排序居然不需要代价
-
手搓下对\(x\sim y\)排序与对\(x\sim z\),\(z+1\sim y\)排序,前者代价为\(y-x\),后者为\(z-x+y-z-1=y-x-1\),由此可得结论,选若干个区间排序相当于把原区间打散成小区间,且打散到单个数是相当于没选这个数,所以可以保证正确性。
-
原问题可转化为对每个子区间里的断点,即\(z\sim z+1\)被打断的这一连系,进行求和,最终在总答案中减去这些位置。(CF原文:原问题等价于求\((l,k,r)\)满足\(max(l\sim k)<min(k+1\sim r)\)的三元组个数)
从这里开始没想到的...
-
此时我们设\((l,k,r)\)中,\(min(k+1\sim r)=a_i\),则我们枚举\(i\),为了使\(max(l\sim k)<min(k+1\sim r)\),必定有\(a_k\)是\(a_i\)左边第一个小于\(a_i\)的数,所以我们可以求出\(k\),没有这样的\(k\)则这个\(i\)贡献为\(0\)。
-
接着我们可以看\(l\)所满足的限制。设\(a_x\)为\(a_k\)左边第一个大于\(a_i\)的数(若没有则\(x=0\)),\(a_y\)为\(a_i\)右边第一个小于\(a_i\)的数(若没有则\(y=n+1\)),我们可以知道,\(i\)此时对所有\(x<l<=k<i<=r<y\)的\((l,r)\)有一的贡献,所以\(i\)的贡献为\((k-x)*(y-i)\)
使用\(st\)表预处理,二分查找,时间复杂度\(O(nlogn)\)
code
#include<cstdio>
#include<algorithm>
#include<iostream>
#define int long long
using namespace std;
const int mn=3e5+5;
int n,a[mn],st[2][mn][20];
int lt[mn],ans;
void init()
{lt[1]=0;for(int i=2;i<=3e5;i++){lt[i]=lt[i>>1]+1;}
}
void build()
{for(int i=1;i<=n;i++){st[0][i][0]=st[1][i][0]=a[i];}for(int i=1;i<20;i++){for(int j=1;j<=n-(1<<i)+1;j++){st[0][j][i]=max(st[0][j][i-1],st[0][j+(1<<(i-1))][i-1]);st[1][j][i]=min(st[1][j][i-1],st[1][j+(1<<(i-1))][i-1]);}}
}
int qry(int x,int l,int r)
{int y=lt[r-l+1];if(x==0)return max(st[x][l][y],st[x][r-(1<<y)+1][y]);return min(st[x][l][y],st[x][r-(1<<y)+1][y]);
}
void solve()
{ans=0;scanf("%lld",&n);for(int i=1;i<=n;i++){scanf("%lld",&a[i]);ans+=(n-i+1)*(n-i)/2;}build();// cerr<<ans<<"-----\n";// int abc=0;for(int i=1;i<=n;i++){int x=0,y=n+1,k=-1;int l=1,r=i-1;while(l<=r){int mid=(l+r)>>1;if(qry(1,mid,i-1)<a[i]){k=mid;l=mid+1;}else{r=mid-1;}}if(k==-1)continue;l=1;r=k-1;while(l<=r){int mid=(l+r)>>1;if(qry(0,mid,k-1)>a[i]){x=mid;l=mid+1;}else{r=mid-1;}}l=i+1;r=n;while(l<=r){int mid=(l+r)>>1;if(qry(1,i+1,mid)<a[i]){y=mid;r=mid-1;}else{l=mid+1;}}// cerr<<ans<<" "<<i<<" "<<x<<" "<<y<<" "<<k<<'\n';ans-=(k-x)*(y-i);}// abc/=2;printf("%lld\n",ans);
}
signed main()
{init();int T;scanf("%lld",&T);while(T--){solve();}
}