多校联合HDU (7月12日 1006 YY's problem)

 

 

YY's new problem

Time Limit: 12000/4000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 1184    Accepted Submission(s): 134


Problem Description
  
  
Given a permutation P of 1 to N, YY wants to know whether there exists such three elements P[i 1], P[i 2], P[i 3] that P[i 1]-P[i 2]=P[i 2]-P[i 3], 1<=i 1<i 2<i 3<=N.
 


 

Input
  
  
The first line is T(T<=60), representing the total test cases. Each test case comes two lines, the former one is N, 3<=N<=10000, the latter is a permutation of 1 to N.
 


 

Output
  
  
For each test case, just output 'Y' if such i 1, i 2, i 3 can be found, else 'N'.
 


 

Sample Input
  
  
2 3 1 3 2 4 3 2 4 1
 


 

Sample Output
  
  
N Y
 


 

Source
  
  
HNU
 
我的暴力方法:

在1到N的排列中检查是否存在满足顺序等差的3个位数,由于题目时限的问题基本暴力还是能过的,我也是用的枚举的方法,时间复杂度是N2 ,跑了不到2S ,每个数字出现的位置,然后枚举Pi2,在Pi2-m与PI2+m之间查找是否存在2个数正好满足位置在Pi2的两侧,既a[j]-pos)*(a[2*i-j]-pos)<0;

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <cstdlib>
#include <vector>
#include <map>
#include <algorithm>

const int maxn = 10005;
bool vis1[maxn],vis2[maxn];
int a[maxn];       
int n,i,j,k;
int main ()
{
    int cas;
    scanf("%d",&cas);
    while (cas--)
    {
        int pos;
        bool flag=false;
        scanf("%d",&n);
        for (i=0 ; i<n ; ++i)
        {
             scanf("%d",&pos);
             a[pos]=i;
        }
        for (i=2 ; i<n; ++i)//emun i2
        {
            int m=((i-1)<(n-i)?(i-1):(n-i));
            pos=a[i];
            //printf("i=%d  m=%d  pos[i]=%d\n",i,m,pos);
            for (j=i-m ; j<i ; ++j)
            if((a[j]-pos)*(a[2*i-j]-pos)<0){/*printf("%d %d %d\n",j,i,2*i-j);*/flag=true;break;}
            if(flag)break;
        }
       
        printf("%c\n",flag?'Y':'N');
    }
    return 0;
}

题解的方法:

(正解)

排列的性质就是1到N的数每个数都会出现,那么就是说,如果对于一个1到N的排列,如果前K个数里面没有X(1<=X<=N),那么后面的N-K个数里面一定会出现X。

等式A[p3]-A[p2]=A[p2]-A[p1]是通过数的位置的描述得出的,而我们发现可以通过从数本身的角度来描述上述等式。

定义Bi表示i这个数在原序列中出现的位置,那么对于一个数Y,需要判断的就变成了对于所有满足2Y=X+Z的X,Z,判断X,Z的出现的位置是否满足要求。

对于这个问题,我们要把它分解成两个小的问题,第一个就是找出所有满足要求的X,Z,第二个就是位置的出现有什么要求。

我们先来看第一个问题,因为2Y=X+Z,那么如果我们把1到N的数全部写在一个数轴上。

1 2 3 4 5 6 7 ……

       假设Y=4,那么容易发现X=3,Z=5;X=2,Z=6;X=1,Z=7都是满足条件的对子

       即满足条件的对子关于Y在数轴上的点轴对称。

       现在我们再来看看第二个问题,位置有什么要求。

       假设X,Z是关于Y的合法对子,那么Bx,Bz一定一个大于By,一个小于By

       现在我们尝试从另一个角度来描述这个问题。

       从这个排列的第一个位置开始往后扫描,设每次扫到的数字为Aq,那么就判断所有满足条件的对子中是否存在一个在1到q中出现,一个在1到q中没有出现。

       我们发现这个算法由于要判断这些对子,而这些对子的总数也是高达O(N2)的。

       那么我们有什么方式可以快速判断多个对子呢?

       前面的分析中用到的数轴,我们继续来用数轴来表示问题。一开始把数轴上1到N的位置都标记为0,表示暂时没有出现,然后每次向后扫描进入一个位置q,就将Aq上标记为1,并且同时判断对子是否存在一个出现,一个没有出现的。

       举一个例子吧,比如{5,1,3,7,2,6,4}我们现在扫描到3了,那么我们来数轴上的情况。

1 2 3 4 5 6 7

1 01 0 1 0 0

我们要判断的对子就是(1,5),(2,4),也就是如果将这个东西看成01串,判断S(1->2),与S(5->4)是否相同。

判断连续的01字符串是否相等可以用Hash表来做,关于如何构造Hash值。

但是求Hash仍然是个O(N2),然后我们可以构造线段树来快速维护Hash(要两个线段树,一个维护正向的hash另一个逆向的hash值),这样我们可以通过一次扫描来判断,时间复杂度O(NlogN).

难度:★★★★

题解的代码:

还是没太看明白,我还是太菜了。。。

#include <iostream>
#include <cstdio>
using namespace std;
int n,m,test;
int f[10007],lt[100007],rt[100007],pr1,pr2;
long long h1[100007],h2[100007],x1,x2;
long long  ch[10007];
bool fd;
void todo(int x,int y,int z)
{
 lt[x]=y;rt[x]=z;h1[x]=h2[x]=0;
 if (y<z)
 {
  todo(x<<1,y,(y+z)>>1);
  todo((x<<1)+1,(y+z)/2+1,z);
 }
}
void fd1(int x,int y,int z)
{
 if (y<=lt[x] && rt[x]<=z)
 {
  int len=rt[x]-lt[x]+1;
  x1=(((long long)x1*ch[len])+h1[x])%pr2;
 }
 else
  if (rt[x]<y || z<lt[x]) return ;
   else
   {
    fd1((x<<1)+1,y,z);
    fd1(x<<1,y,z);
   }
}
void fd2(int x,int y,int z)
{
 if (y<=lt[x] && rt[x]<=z)
 {
  int len=rt[x]-lt[x]+1;
  x2=(((long long)x2*ch[len])+h2[x])%pr2;
 }
 else
  if (rt[x]<y || z<lt[x]) return ;
   else
   {
    fd2(x<<1,y,z);
    fd2((x<<1)+1,y,z);
   }
}
void into(int x,int y)
{
 if (lt[x]!=rt[x])
 {
  if (y<=(lt[x]+rt[x])/2) into(x<<1,y);
   else     into((x<<1)+1,y);
  int len1=rt[(x<<1)]-lt[(x<<1)]+1;
  h1[x]=(((long long)h1[(x<<1)+1])*ch[len1]+h1[x<<1])%pr2;
  int len2=rt[(x<<1)+1]-lt[(x<<1)+1]+1;
  h2[x]=(((long long)h2[x<<1])*ch[len2]+h2[(x<<1)+1])%pr2;
 }
 else
 {
  h1[x]=1;h2[x]=1;
 }
}

int main()
{
    double st = clock();
 //freopen("YY's problem.in","r",stdin);
 //freopen("YY's problem.out","w",stdout);
 pr1=2317;pr2=999991;
 ch[0]=1;
 for (int i=1;i<=10000;i++) ch[i]=(((long long)ch[i-1])*pr1)%pr2;
 for (cin>>test;test;test--)
 {
  cin>>n;fd=true;
  for (int i=1;i<=n;i++) scanf("%d",&f[i]);
  todo(1,1,n);
  for (int i=1;i<=n;i++)
  {
   x1=x2=0;
   if (f[i]!=1 && f[i]!=n)
   if (n-f[i]>f[i]-1)
   {
    fd1(1,f[i]+1,f[i]*2-1);
    fd2(1,1,f[i]-1);
    if (x1!=x2) {fd=false;break;}
   }
   else
   {
    fd1(1,f[i]+1,n);
    fd2(1,f[i]-(n-f[i]),f[i]-1);
    if (x1!=x2) {fd=false;break;}
   }
   into(1,f[i]);
  }
  if (fd) printf("N\n");
   else   printf("Y\n");
 }
 cout<<" time = "<<clock() - st<<endl;
 //fclose(stdin);
 //fclose(stdout);
 return 0;
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值