【题目描述 Description】
给一个 1 到 N 的排列{Ai},询问是否存在 1<=p1<p2<p3<p4<p5<…<pLen<=N(Len>=3),使得 Ap1,Ap2,Ap3,…ApLen 是一个等差序列。
【输入描述 Input Description】
输入的第一行包含一个整数 T,表示组数。
下接 T 组数据,每组第一行一个整数 N,每组第二行为一个 1 到 N 的排列, 数字两两之间用空格隔开。
【输出描述 Output Description】
对于每组数据,如果存在一个等差子序列,则输出一行“Y”,否则输出一 行“N”。
【样例输入 Sample Input】
2
3
1 3 2
3
3 2 1
【样例输出 Sample Output】
N
Y
【数据范围及提示 Data Size & Hint】
对于5%的数据,N<=100,对于30%的数据,N<=1000,对于100%的数据,N<=10000,T<=7
【解题思路】
首先声明,此题开始并没有什么思路,只找到一个O(N^2)的算法,然而这并没有什么卵用。
老师明示暗示我要我用线段树去做,我苦思冥想没有想出来,于是就抄了题解。
题解是这样的,枚举等差中项,用一颗线段树去维护那些值选了,那些值没选,构成一个01串之后求一个哈希值。
如果出现中项左边的hash值和右边的hash值不一样的情况,就说明存在等差数列,因为证明有一个值在中项左边已经选过,并且与其对应的值在中项右边还没有选。
插入O(logn),查询O(logn),扫一遍O(n)整体O(ologn);
代码略丑
#include<cstdio> #include<cstring> #include<iostream> using namespace std; const int maxn=10000+10, mod=100000007; int xp[maxn],a[maxn],n,v,t; long long sumv[4*maxn][2]; //sumv[i][0] 代表从左边扫的值,sumv[i][1]代表从右边扫的值 void updata(int u,int l,int r){ int lc=u<<1,rc=lc+1; if (l==r) sumv[u][0]=sumv[u][1]=1; else{ int mid=(l+r)/2; if (v<=mid) updata(lc,l,mid); else updata(rc,mid+1,r); sumv[u][0]=(sumv[rc][0]+xp[r-mid]*sumv[lc][0]%mod)%mod; sumv[u][1]=(sumv[lc][1]+xp[mid-l+1]*sumv[rc][1]%mod)%mod; } } long long query(int node,int l,int r,int a,int b,int x){ int lc=node<<1,rc=lc+1; if (l==a&&r==b) return sumv[node][x]; int mid=(l+r)/2; long long left=0,right=0; if (mid<b) right=query(rc,mid+1,r,max(mid+1,a),b,x); if (a<=mid) left=query(lc,l,mid,a,min(mid,b),x); return (x?left+right*xp[max(0,mid-a+1)]%mod:right+left*xp[max(0,b-mid)]%mod)%mod; } int main(){ scanf("%d",&t); for (int ii=0;ii<t;ii++){ memset(sumv,0,sizeof(sumv)); bool flag=0; scanf("%d",&n); xp[0]=1; for (int i=1;i<=n+5;i++) xp[i]=(xp[i-1]<<1)%mod; for (int i=0;i<n;i++)scanf("%d",&a[i]); for (int i=0;i<n;i++){ int x=a[i]; int len=min(x-1,n-x);//长度取短之后比较 if (len) { int t1=query(1,1,n,x+1,x+len,1); int t2=query(1,1,n,x-len,x-1,0); if (t1!=t2){ flag=1; break; } } v=x; updata(1,1,n); } if (flag) printf("Y\n"); else printf("N\n"); } }
时间: 2024-11-07 16:05:41