D e s c r i p t i o n Description Description
给出一段长度为 n n n的序列,要求找出一段区间 [ L ∼ R ] [L\sim R] [L∼R],使得该区间内的所有 a i ( L ≤ i ≤ R ) a_i({L\leq i\leq R}) ai(L≤i≤R),都能被 a k ( L ≤ k ≤ R ) a_k(L\leq k\leq R) ak(L≤k≤R)整除,求最长的区间的长度及这些区间(可能有多个)
80%
n
≤
1
0
5
n\leq10^5
n≤105
100%
n
≤
5
×
1
0
5
n\leq 5\times 10^5
n≤5×105
S o l u t i o n Solution Solution
显然若一段区间的 g c d gcd gcd=该区间的最小值,那这个区间是满足的
发现若存在长度为 k k k的合法序列,则也一定存在长度为 k − 1 ( k > 1 ) k-1(k>1) k−1(k>1)的合法序列,根据这个性质,我们可以二分答案
一开始我打了一棵线段树(时间复杂度: O ( n l o g 3 n ) O(nlog^3n) O(nlog3n),线段树本身带 l o g log log,二分带 l o g log log, g c d gcd gcd也带 l o g log log,所以是三次方),打完后稳稳的过了样例,但是!当我制作500000的数据跑时,发现它竟然跑了4800ms(平均每个点),那么加O2也有2500ms(最快的一个点),虽然时限有2000ms(每个点),但我还是慌得一匹。
后来我观察发现其是没有区间修改的,于是我就联想到了 R M Q RMQ RMQ,昨天又正好看完 z k w zkw zkw线段树的博客,知道 g c d gcd gcd是满足区间加法的,也就是说,我们可以用 S T ST ST表预处理区间 g c d gcd gcd和区间最小值,然后二分中间+判断,就可以 O ( n l o g 2 n + n l o g 2 n ) O(nlog^2n+nlog^2n) O(nlog2n+nlog2n)( g c d gcd gcd带 l o g log log,预处理本来就有 l o g log log,后面的二分一个 l o g log log,求区间 g c d gcd gcd是 l o g log log的复杂度)A掉本题啦
经线下对拍测试,100000的数据线段树800+ms,ST表接近300ms(均没开O2)
500000的数据线段树4800ms,ST表接近900ms(没开O2)
500000的数据线段树2600ms+,ST表不超过700ms(开了O2)
据说我们这届有用O(n)过掉的,但是不重要:反正题解的解和我做出来的是一样的
压(绝对不是那个ya)王(压缩代码之王,也是卡常之王)用 z k w zkw zkw线段树卡常过了这道题,你们可以去抠它博客
C o d e Code Code( S e g m e n t T r e e Segment\ Tree Segment Tree)
#include<cstdio>
#include<cctype>
#include<vector>
#define N 500001
#define lson (k<<1)
#define rson (k<<1|1)
using namespace std;int n,l,r,mid,maxn;
vector<int>ans[N];
inline char Getchar()
{
static char buf[10000000],*p1=buf+10000000,*pend=buf+10000000;
if(p1==pend)
{
p1=buf; pend=buf+fread(buf,1,10000000,stdin);
if (pend==p1) return -1;
}
return *p1++;
}
inline long long read()
{
char c;int d=1;long long f=0;
while(c=Getchar(),!isdigit(c))if(c==45)d=-1;f=(f<<3)+(f<<1)+c-48;
while(c=Getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
return d*f;
}
inline int gcd(int x,int y){return y?gcd(y,x%y):x;}
inline int min(int x,int y){return x<y?x:y;}
inline int max(int x,int y){return x>y?x:y;}
int minn[N<<2],gcdn[N<<2],a[N];
inline void up(int k){gcdn[k]=gcd(gcdn[lson],gcdn[rson]);minn[k]=min(minn[lson],minn[rson]);return;}//上传
inline void build(int k,int l,int r)//建树
{
if(l==r) {;a[l]=read();ans[0].push_back(l);gcdn[k]=a[l];minn[k]=a[l];return;}
int mid=l+r>>1;
build(lson,l,mid);build(rson,mid+1,r);
up(k);
return;
}
inline int askgcd(int k,int l,int r,int ql,int qr)//查询区间gcd
{
if(ql<=l&&r<=qr) return gcdn[k];
int mid=l+r>>1;
if(qr<=mid) return askgcd(lson,l,mid,ql,qr);
if(ql>mid) return askgcd(rson,mid+1,r,ql,qr);
return gcd(askgcd(lson,l,mid,ql,mid),askgcd(rson,mid+1,r,mid+1,qr));
}
inline int askmin(int k,int l,int r,int ql,int qr)//查询区间最小值
{
if(ql<=l&&r<=qr) return minn[k];
int mid=l+r>>1;
if(qr<=mid) return askmin(lson,l,mid,ql,qr);
if(ql>mid) return askmin(rson,mid+1,r,ql,qr);
return min(askmin(lson,l,mid,ql,mid),askmin(rson,mid+1,r,mid+1,qr));
}
bool check(int x)//判断是否存在长度为m的区间
{
if(x==1) return true;
bool ok=false;
for(register int i=1;i+x<=n+1;i++)
if(askgcd(1,1,n,i,i+x-1)==askmin(1,1,n,i,i+x-1)) //存在
{
ok=true;ans[x-1].push_back(i);//用vector保存答案
maxn=max(maxn,x-1);//取最优解
}
return ok;
}
signed main()
{
freopen("1.txt","r",stdin);
freopen("1.ans","w",stdout);
n=read();
build(1,1,n);//建树
l=1;r=n;
while(l<=r)//二分
{
int mid=(l+r)>>1;
if(check(mid)) l=mid+1;else r=mid-1;
}
printf("%d %d\n",ans[maxn].size(),maxn);
for(register int i=0;i<ans[maxn].size();i++) printf("%d ",ans[maxn][i]);//输出
}
C o d e Code Code(ST表)
#include<cstdio>
#include<cctype>
#include<vector>
#define N 500001
#define lson (k<<1)
#define rson (k<<1|1)
using namespace std;int n,l,r,mid,maxn;
vector<int>ans[N];
inline char Getchar()
{
static char buf[10000000],*p1=buf+10000000,*pend=buf+10000000;
if(p1==pend)
{
p1=buf; pend=buf+fread(buf,1,10000000,stdin);
if (pend==p1) return -1;
}
return *p1++;
}
inline long long read()
{
char c;int d=1;long long f=0;
while(c=Getchar(),!isdigit(c))if(c==45)d=-1;f=(f<<3)+(f<<1)+c-48;
while(c=Getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
return d*f;
}
inline int gcd(int x,int y){return y?gcd(y,x%y):x;}
inline int min(int x,int y){return x<y?x:y;}
inline int max(int x,int y){return x>y?x:y;}
int fmin[N][24],fgcd[N][24],a[N],log[N];
void work()//ST表预处理
{
for(int j=1;1<<j<=n;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
fmin[i][j]=min(fmin[i][j-1],fmin[i+(1<<j-1)][j-1]),
fgcd[i][j]=gcd(fgcd[i][j-1],fgcd[i+(1<<j-1)][j-1]);
}
int minn(int x,int y)
{
int z=log[y-x+1];
return min(fmin[x][z],fmin[y-(1<<z)+1][z]);
}
int gcdn(int x,int y)
{
int z=log[y-x+1];
return gcd(fgcd[x][z],fgcd[y-(1<<z)+1][z]);
}
bool check(int x)//判断是否合法
{
if(x==1) return true;
bool ok=false;
for(register int i=1;i+x<=n+1;i++)
if(gcdn(i,i+x-1)==minn(i,i+x-1))
{
ok=true;ans[x-1].push_back(i);
maxn=max(maxn,x-1);
}
return ok;
}
signed main()
{
freopen("1.txt","r",stdin);
freopen("1.out","w",stdout);
n=read();
for(register int i=1;i<=n;i++) fmin[i][0]=fgcd[i][0]=read(),ans[0].push_back(i);
work();
log[2]=1;
for(register int i=3;i<=n;i++) log[i]=log[i>>1]+1;//记得要预处理log,否则你会像wyc大佬一样TLE
l=1;r=n;
while(l<=r)//二分答案
{
int mid=(l+r)>>1;
if(check(mid)) l=mid+1;else r=mid-1;
}
printf("%d %d\n",ans[maxn].size(),maxn);
for(register int i=0;i<ans[maxn].size();i++) printf("%d ",ans[maxn][i]);
}
测试点数据制造
#include<cstdlib>
#include<cstdio>
#include<ctime>
using namespace std;int n=500000;//这里改你想测试的数据范围
signed main()
{
freopen("1.txt","w",stdout);
srand(time(0));
printf("%d\n",n);
for(register int i=1;i<=n;i++) printf("%d ",2+rand()%9);//最好不要太大,因为那样结果会比较小,当然如果你想试一下最大数据过不过的了也是可以的
}
对拍程序
#include<ctime>
#include<cstdio>
#include<cstdlib>
using namespace std;double t1,t2,t3;int i;
signed main()
{
for(register int i=1;;i++)
{
system("rand1.exe");
t1=clock();
system("bf1.exe");
t2=clock();
system("251.exe");
t3=clock();
if(system("fc 1.ans 1.out")) return printf("data #%3d: result: WA\n",i)&0;
printf("data #%3d: result: AC Bf takes %0.1lf ms ST take %0.1lf ms\n",i,t2-t1,t3-t2);
//翻译:测试点信息 # 结果: 线段树(这里称为bf暴力)用时: ms,ST表用时: ms
}
}
蒟蒻制作不易,各位大佬转载麻烦知会作者一声,谢谢!