codeforces 1156E Special Segments of Permutation
题目链接:https://codeforc.es/contest/1156/problem/E
题目大意:
在数组p中可以找到多少个不同的l,r满足。
思路:
ST表+并查集。
ST表还是需要的,因为nlongn的预处理就可以O(1)查询。枚举所有的区间也就O(n^2)。
因为是输入固定1-n,所以我可以设一个y数组表示数组p的值所对应的下标。
(习惯输入为x数组)
我们考虑一个区间[l,r]这个区间最大值为i。(这里用ST表可以找到)
[l,r]对答案的贡献为最大值i左边找一个值a,右边找一个值b,满足a+b==i。有多少个不同的a,b对。
我们可以枚举i左右两边长度小的区间。然后在大区间上找。(小区间就可以少枚举点不是吗?最坏左右区间长度一样总共也就nlongn次)。
然后的问题是怎样找快速查找。
我们用并查集。
把大区间的所有值加入在大区间的最大值上。
你一定疑惑这样做还是会超时的啊。
如果在我们得到这个区间对答案的贡献后,把i左右两区间的最大值加在i上就可以完美的解决这个问题了。
我们直接从区间[1,n]开始,向下遍历,稍微注意一下边缘条件就OK啦。
整个就像一个线段树呢。
人太垃圾,ST表直接复制的板子,如有抄袭希望可以交个朋友哈~~
#include<bits/stdc++.h> using namespace std; #define IOS ios::sync_with_stdio(false); #define int long long #define N 200100 #define mod 1000000007 int x[N]; int y[N]; int pre[N]; int find(int x) { int r=x; while(pre[r]!=r) r=pre[r]; int i=x,j; while(i!=r) { j=pre[i]; pre[i]=r; i=j; } return r; } void join(int x,int y) { int a=find(x); int b=find(y); if(a!=b) { pre[a]=b; } } void into() { for(int i=1; i<N; i++) { pre[i]=i; } } int st[N][22]; void init(int n) { n++; for (int i = 0; i < n; i++) st[i][0] = x[i]; for (int j = 1; (1 << j) <= n; j++) { for (int i = 0; i + (1 << j) - 1 < n; i++) st[i][j] = max(st[i][j - 1],st[i + (1 << (j - 1))][j - 1]); } } int search(int l, int r) { int k = (int)(log((double)(r - l + 1)) / log(2.0)); return max(st[l][k],st[r - (1 << k) + 1][k]); } int Ans; void dfs(int i,int l,int r) { int a=0,b=0; if(y[i]>l) { a=search(l,y[i]-1); //cout<<i<<" "<<a<<endl; dfs(a,l,y[i]-1); } if(y[i]<r) { b=search(y[i]+1,r); //cout<<i<<" "<<b<<endl; dfs(b,y[i]+1,r); } if(r-y[i]<y[i]-l) { for(int j=y[i]+1; j<=r; j++) { if(find(i-x[j])==a) { Ans++; } } } else { for(int j=l; j<=y[i]-1; j++) { if(find(i-x[j])==b) { Ans++; } } } if(a) join(a,i); if(b) join(b,i); } signed main() { IOS; into(); int n,a; cin>>n; for(int i=1; i<=n; i++) { cin>>x[i]; y[x[i]]=i; } init(n); dfs(n,1,n); cout<<Ans; return 0; }