某次模拟赛 数字对
题目描述 Description |
小 H 是个善于思考的学生,现在她又在思考一个有关序列的问题。 |
输入描述 Input Description |
第一行,一个整数 n. |
输出描述 Output Description |
第一行两个整数,num 和 val,表示价值最大的特殊区间的个数以及最大价值。 |
样例输入 Sample Input |
5 |
样例输出 Sample Output |
1 3 |
数据范围及提示 Data Size & Hint |
30%: 1 <= n <= 30 , 1 <= ai <= 32. |
之前的一些废话:暑假作业发现还有论语视频没有看,十分慌张
题解:传说中的暴力踩标程。
解法1:先说正解吧,首先最大价值是可以进行二分的,如何判断解是否可行呢?当前解是mid,枚举每一个长度为mid+1的区间,判这个区间是否合法,如果合法就把该区间左端放入队列中,最后检查队列中是否有元素即可(即有没有符合的区间)至于如何判区间是否合法,根据题意只要区间最小值与区间GCD相等就可以,这个可以通过RMQ来处理,O(n log n)预处理,O(1)查询,至于区间GCD通过类似的方法处理即可。 总复杂度O(n log n*log n)
解法2:再说暴力。对于序列中每一个元素进行考虑,假定当前数为ak,那么我们需要计算出ak所属于的最长区间。向左向右分别扫,如果碰到一个数不能整除ak就停,最后计算出该区间长度[L,R]。然后还有一个优化,处理完第i个数后,我们没有必要继续处理第i+1个数,而是直接从第I个数所对应的R的下一个数开始处理即可,因为我们可以证明,从第i+1个数到第R个数中选它们做ak的答案显然没有拿第i个数当ak更优。复杂度O(♂)
解法3:还有一种类似暴力的做法,该做法与暴力做法的区别就是ak不用往左往右扫了,而是通过二分来确定区间长度,不过也需要区间GCD预处理。复杂度O(n log n*log n)
最后实测结果是解法2速度最快 ,解法1第二 解法3第三
代码:
解法1&&解法2的:
#include<iostream> #include<cmath> #include<cstring> #include<cstdio> #include<queue> #include<algorithm> using namespace std; typedef long long LL; typedef pair<int,int> PII; #define mem(a,b) memset(a,b,sizeof(a)) inline int read() { int x=0,f=1;char c=getchar(); while(!isdigit(c)){if(c=='-')f=-1;c=getchar();} while(isdigit(c)){x=x*10+c-'0';c=getchar();} return x*f; } const int maxn=500010,MAXlog=20; int n,a[maxn],f[maxn],ans,cnt,Min[MAXlog][maxn],GCD[MAXlog][maxn],len[maxn],L,R,A[maxn],l; bool ok; int gcd(int a,int b){return b==0 ? a : gcd(b,a%b);} int MINquery(int L,int R) { int bin=len[R-L+1]; return min(Min[bin][L],Min[bin][R-(1<<bin)+1]); } int GCDquery(int L,int R) { int bin=len[R-L+1]; return gcd(GCD[bin][L],GCD[bin][R-(1<<bin)+1]); } bool check(int index) { l=0; for(int i=1;i+index-1<=n;i++)if(GCDquery(i,i+index-1)==MINquery(i,i+index-1))A[l++]=i; if(l)ans=max(ans,index); return l; } int main() { n=read(); for(int i=1;i<=n;i++)Min[0][i]=GCD[0][i]=a[i]=read(); for(int i=1;(1<<i)<=n;i++) for(int j=1;j+(1<<(i-1))<=n;j++) { Min[i][j]=min(Min[i-1][j],Min[i-1][j+(1<<(i-1))]); GCD[i][j]=gcd(GCD[i-1][j],GCD[i-1][j+(1<<(i-1))]); } for(int i=2;i<=n;i++)len[i]=len[i>>1]+1; L=1;R=n; while(R-L>1) { int mid=(L+R)>>1; if(check(mid))L=mid; else R=mid; } check(L);check(R);check(ans); printf("%d %d\n",l,ans-1); for(int i=0;i<l;i++) { if(!ok)printf("%d",A[i]),ok=1; else printf(" %d",A[i]); } printf("\n"); return 0; }
#include<iostream> #include<cmath> #include<cstring> #include<cstdio> #include<queue> #include<algorithm> using namespace std; typedef long long LL; typedef pair<int,int> PII; #define mem(a,b) memset(a,b,sizeof(a)) inline int read() { int x=0,f=1;char c=getchar(); while(!isdigit(c)){if(c=='-')f=-1;c=getchar();} while(isdigit(c)){x=x*10+c-'0';c=getchar();} return x*f; } const int maxn=500010; int n,a[maxn],f[maxn],ans,cnt; bool ok; int gcd(int a,int b){return b==0 ? a : gcd(b,a%b);} int main() { n=read(); for(int i=1;i<=n;i++)a[i]=read(); for(int i=1;i<=n;i++) { int L=i,R=i; for(L=i;L>0;L--)if(a[L]%a[i]!=0)break; for(R=i;R<=n;R++)if(a[R]%a[i]!=0)break; L++;R--; f[L]=R-L; i=R; } for(int i=1;i<=n;i++)ans=max(ans,f[i]); for(int i=1;i<=n;i++)if(f[i]==ans)cnt++; printf("%d %d\n",cnt,ans); for(int i=1;i<=n;i++) if(f[i]==ans) { if(!ok)printf("%d",i),ok=1; else printf(" %d",i); } printf("\n"); return 0; }
解法3懒得写了。
总结:考试时候想到了解法2,但是没有勇气去写,像这种代码复杂度又低却跑得贼快的写起来真是太值了。