2019.01.25【NOIP提高组】模拟B组 JZOJ 3895 数字对

D e s c r i p t i o n Description Description

给出一段长度为 n n n的序列,要求找出一段区间 [ L ∼ R ] [L\sim R] [LR],使得该区间内的所有 a i ( L ≤ i ≤ R ) a_i({L\leq i\leq R}) ai(LiR),都能被 a k ( L ≤ k ≤ R ) a_k(L\leq k\leq R) ak(LkR)整除,求最长的区间的长度及这些区间(可能有多个)

80% n ≤ 1 0 5 n\leq10^5 n105
100% n ≤ 5 × 1 0 5 n\leq 5\times 10^5 n5×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) k1(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
	}
}

蒟蒻制作不易,各位大佬转载麻烦知会作者一声,谢谢!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值