[CODEVS1283]等差子序列解题报告
2014-12-01 21:06
260 查看
这真的是一道很神的题,但在CODEVS。。可以暴力过掉。。当然,在BZOJ上是不可以的。
所以。。我还是看了看题解,题解是这样说的:
一、一个基本的转化是将题目中的描述翻译为一个长度为3的等差子序列,即存在x,k,使得x-k与x+k在x异侧。
二、我们先来看一个错误的贪心思路,因为是一个1~N的排列,所以我们可以把它们视为离散后的数据,首先我们将其按奇偶分开,奇数放一边,偶数放一边,这样就可以保证不会有跨两侧的等差子序列,然后我们再将两侧的子序列离散,以类似的思路令其也符合上述要求,那么我们就可以得到一个没有等差子序列的排列。
然后我们再按照上述思路逆check即可。
但问题是,其逆命题真的成立么?难道就不可能有不符合上述要求的排列,但它之中也不存在等差子序列么?
答案是否定的!
一个显然的例子是2、1、4、3;
如果你认为这是因为它长度为4的话,那么下面给出一个长度为6的反例:
5,6,1,3,2,4
但是这种贪心对于随机数据的正确率是相当高的,事实上,BZOJ的数据只能卡掉其在4、5时的贪心,所以如果用这种思路写的话。。只要把4、5时的不为等差子序列的情况预先打个表就可以AC了。
但这种下流的做法显然不是我们想要的。。So。。
四、让我们来看看正解吧!
一个非常显然的思路在①中已经给出,笔者看到本题的时候也正是如此写的暴力,但这种暴力不可以说真正意义上A掉这道题的,不过呢,其实我们可以对它进行改进。
考虑如何对一个数x以极低的复杂度判断其x-k与x+k,k∈(0,min(x-1,N-x))是否在x两侧?
①换个角度考虑,两侧的反面就是一侧,即它们在x的左侧或右侧,这有什么好处呢?
假如我们用一个布尔数组来记录每一个数x的出现情况的话,那么我们就可以离线地从左到右扫描每一个数x,判断从x-1到最左边与从x+1到最右边是否一样即可,如果出现不同了,就说明出现了等差子序列。如果相同的话,就意味着以x为中项的等差子序列是不存在的,因为其可能的首项和末项要么都在其之前出现了(均为1),要么都在其之后出现了(均为0)。
于是我们发现,我们可以用两个二进制数来表示x-k与x+k的出现情况,然后check这两个二进制数。
②但是N有10000呢,太大了!这可怎么办?Hash!最简单的hash就好了,我们可以保存一个01串,然后需要的时候计算它的二进制值,在计算的时候模一个大质数就好了。——补充,多年后——其实还有一种更好的方法,就是bitset!比较一下时间复杂度bitset:O(TN^2/100)≈7*10^6,线段树:O(TNlogN)≈9*10^5+大常数。妈蛋似乎还是差了很多,但是这道题N这么小,bitset完全没有问题啊。
③但是。。这样的复杂度依然是O(N^2T)≈10^9的,这时我们发现我们其实是在对一个线段求hash,而根据我们的方法,两个线段的hash值是可以合并的,于是这个问题满足分治解决的要求,那么我们是完全可以为原先的扫描线配上线段树以加速hash过程的,于是正解便呼之欲出了!
综,本题的正解就是扫描线+线段树+hash!
五、很容易犯错的地方:
在询问的时候,合并线段信息一定要头脑清晰,
①是正着来还是倒着来别搞混了。
②当询问落到这个线段树的某个节点的时候,询问区间分为在它的左儿子、在它的右儿子、在它的左右儿子三种情况,最好是分开写比较条理清晰,我想把它们合并起来结果一不小心就蛋痛了。。
暴力:
贪心:
线段树:
所以。。我还是看了看题解,题解是这样说的:
一、一个基本的转化是将题目中的描述翻译为一个长度为3的等差子序列,即存在x,k,使得x-k与x+k在x异侧。
二、我们先来看一个错误的贪心思路,因为是一个1~N的排列,所以我们可以把它们视为离散后的数据,首先我们将其按奇偶分开,奇数放一边,偶数放一边,这样就可以保证不会有跨两侧的等差子序列,然后我们再将两侧的子序列离散,以类似的思路令其也符合上述要求,那么我们就可以得到一个没有等差子序列的排列。
然后我们再按照上述思路逆check即可。
但问题是,其逆命题真的成立么?难道就不可能有不符合上述要求的排列,但它之中也不存在等差子序列么?
答案是否定的!
一个显然的例子是2、1、4、3;
如果你认为这是因为它长度为4的话,那么下面给出一个长度为6的反例:
5,6,1,3,2,4
但是这种贪心对于随机数据的正确率是相当高的,事实上,BZOJ的数据只能卡掉其在4、5时的贪心,所以如果用这种思路写的话。。只要把4、5时的不为等差子序列的情况预先打个表就可以AC了。
但这种下流的做法显然不是我们想要的。。So。。
四、让我们来看看正解吧!
一个非常显然的思路在①中已经给出,笔者看到本题的时候也正是如此写的暴力,但这种暴力不可以说真正意义上A掉这道题的,不过呢,其实我们可以对它进行改进。
考虑如何对一个数x以极低的复杂度判断其x-k与x+k,k∈(0,min(x-1,N-x))是否在x两侧?
①换个角度考虑,两侧的反面就是一侧,即它们在x的左侧或右侧,这有什么好处呢?
假如我们用一个布尔数组来记录每一个数x的出现情况的话,那么我们就可以离线地从左到右扫描每一个数x,判断从x-1到最左边与从x+1到最右边是否一样即可,如果出现不同了,就说明出现了等差子序列。如果相同的话,就意味着以x为中项的等差子序列是不存在的,因为其可能的首项和末项要么都在其之前出现了(均为1),要么都在其之后出现了(均为0)。
于是我们发现,我们可以用两个二进制数来表示x-k与x+k的出现情况,然后check这两个二进制数。
②但是N有10000呢,太大了!这可怎么办?Hash!最简单的hash就好了,我们可以保存一个01串,然后需要的时候计算它的二进制值,在计算的时候模一个大质数就好了。——补充,多年后——其实还有一种更好的方法,就是bitset!比较一下时间复杂度bitset:O(TN^2/100)≈7*10^6,线段树:O(TNlogN)≈9*10^5+大常数。妈蛋似乎还是差了很多,但是这道题N这么小,bitset完全没有问题啊。
③但是。。这样的复杂度依然是O(N^2T)≈10^9的,这时我们发现我们其实是在对一个线段求hash,而根据我们的方法,两个线段的hash值是可以合并的,于是这个问题满足分治解决的要求,那么我们是完全可以为原先的扫描线配上线段树以加速hash过程的,于是正解便呼之欲出了!
综,本题的正解就是扫描线+线段树+hash!
五、很容易犯错的地方:
在询问的时候,合并线段信息一定要头脑清晰,
①是正着来还是倒着来别搞混了。
②当询问落到这个线段树的某个节点的时候,询问区间分为在它的左儿子、在它的右儿子、在它的左右儿子三种情况,最好是分开写比较条理清晰,我想把它们合并起来结果一不小心就蛋痛了。。
暴力:
#include<iostream> using namespace std; #include<cstring> #include<cmath> #include<algorithm> #include<cstdio> char * ptr=new char[1000000]; inline void in(short &x){ while(*ptr<'0'||*ptr>'9')++ptr; x=0; while(*ptr>47&&*ptr<58)x=x*10+*ptr++-'0'; } int main(){ short T,n,i,j,a[10001],tmp,k; fread(ptr,1,1000000,stdin); in(T); while(T--){ in(n); for(i=0;i<n;++i){ in(tmp); a[tmp]=i; } bool flag=0; for(i=2;i<n;++i) for(j=i,k=i;--j&&++k<=n;) if(a[j]>a[i]){ if(a[i]>a[k]){ flag=1; break; } } else if(a[j]<a[i]) if(a[i]<a[k]){ flag=1; break; } if(flag)printf("Y\n"); else printf("N\n"); } }
贪心:
#include<iostream> using namespace std; #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> int a[10000],tmpsort[10000],p[10000],N,tmpwork[10000]; bool flag4[4000],flag5[50000]; char * ptr=(char *)malloc(1000000); inline void in(int &x){ while(*ptr<'0'||*ptr>'9')++ptr; x=0; while(*ptr>47&&*ptr<58)x=x*10+*ptr++-'0'; } inline int getint(int * a,int l,int r){ int x=0; for(int i=l;i<=r;++i)x=x*10+a[i]; return x; } inline bool check(int l,int r){ //printf("%d,%d\n",l,r); if(r-l<2)return 1; int tot=0,i=l; for(;i<=r;++i)tmpsort[tot++]=a[i]; sort(tmpsort,tmpsort+tot); for(i=0;i<tot;++i)p[tmpsort[i]]=i; for(i=l;i<=r;++i)tmpwork[i]=p[a[i]]; if(r-l+1==4)return flag4[getint(tmpwork,l,r)]; if(r-l+1==5)return flag5[getint(tmpwork,l,r)]; for(i=l;i<=r;++i)tmpwork[i]=tmpwork[i]&1; if(tmpwork[l]==tmpwork[r])return 0; int m=(r-l+1)>>1; for(i=1;i<m;++i){ //cout<<l<<","<<r<<":"<<l+i<<endl; if(tmpwork[l]!=tmpwork[l+i]) return 0; } for(i=1;i<m;++i){ //cout<<l<<","<<r<<":"<<r-i<<endl; if(tmpwork[r]!=tmpwork[r-i]) return 0; } m=(l+r)>>1; if(tmpwork[m]!=tmpwork[l])--m; //cout<<l<<":"<<tmpwork[l]<<"("<<a[l]<<") "<<m<<":"<<tmpwork[m]<<"("<<a[m]<<")\n", return check(l,m)&&check(m+1,r); } inline bool check(){ for(int i=0;i<N;++i) for(int j=i+1;j<N;++j) for(int k=j+1;k<N;++k) if(a[k]-a[j]==a[j]-a[i]) return 0; return 1; } int main(){ int T; //-------pre-work-------- for(int i=0;i<5;++i)a[i]=i; N=4; while(next_permutation(a,a+N)) if(check()) flag4[getint(a,0,N-1)]=1; N=5; while(next_permutation(a,a+N)) if(check()) flag5[getint(a,0,N-1)]=1; //-------work----------- return 0; fread(ptr,1,1000000,stdin); in(T); while(T--){ in(N); for(int i=0;i<N;++i)in(a[i]); if(check(0,N-1))printf("N\n"); else printf("Y\n"); } }
线段树:
#include<iostream> using namespace std; #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #define root 1,1,N #define lson node<<1,l,(l+r)>>1 #define rson node<<1|1,((l+r)>>1)+1,r #include<bitset> const int P=100000007; typedef long long lld; lld tree[40000][2],mi[10001]; void update(int node,int l,int r,int x){ if(l==r){ tree[node][0]=1; tree[node][1]=1; } else{ int m=(l+r)>>1; if(x>m)update(rson,x); else update(lson,x); tree[node][1]=(tree[node<<1][1]+tree[node<<1|1][1]*mi[((l+r)>>1)-l+1]%P)%P; tree[node][0]=(tree[node<<1|1][0]+tree[node<<1][0]*mi[r-((l+r)>>1)]%P)%P; } } lld query(int node,int l,int r,int a,int b,int x){ if(l==a&&r==b)return tree[node][x]; int m=(l+r)>>1; lld left=0,right=0; if(m<b)right=query(rson,max(m+1,a),b,x); if(a<=m)left=query(lson,a,min(m,b),x); return (x?left+right*mi[max(0,m-a+1)]%P:right+left*mi[max(0,b-m)]%P)%P; } int main(){ int T,i,x,len,N; lld tmp1,tmp2; mi[0]=1; for(i=1;i<10001;++i)mi[i]=(mi[i-1]<<1)%P; scanf("%d",&T); while(T--){ scanf("%d",&N); memset(tree,0,sizeof(tree)); for(i=0;i<N;++i){ scanf("%d",&x); len=min(x-1,N-x); if(len&&query(root,x-len,x-1,0)!=query(root,x+1,x+len,1)){ printf("Y\n"); break; } update(root,x); } if(i==N)printf("N\n"); for(++i;i<N;++i)scanf("%*d"); } }
相关文章推荐
- codevs 2622 数字序列 DP 解题报告
- Codevs 1283 等差子序列
- [codevs1283]等差子序列(二进制)
- codevs 1506 传话 Tarjan 解题报告
- codevs 1098 均分纸牌 模拟 解题报告
- codevs1298 凸包周长 解题报告
- codevs 1966 乘法游戏 区间DP 解题报告
- codevs 1001 舒适的路线 玄学方法 解题报告
- 最小和[CODEVS1635]解题报告
- Codevs5230【三校联考试题】 猴子(重庆一中高2018级信息学竞赛测验8) 解题报告
- codevs 1282 约瑟夫问题 大暴力? 解题报告
- CodeVS 3657 区间DP 解题报告
- 【codevs2094/usaco】 量取牛奶 解题报告
- [CODEVS1060]搞笑世界杯 解题报告
- codevs 1961 躲避大龙 解题报告 SPFA
- codevs 1001 舒适的路线 解题报告 (枚举+并查集)
- codevs 1253 超级市场 DP 解题报告
- [CODEVS1159]最大全0子矩阵解题报告
- [数论][CODEVS 1497 取余运算]解题报告
- codevs 1219 骑士游历 DP 解题报告