【题解】 Ironforge

Inforge

题目描述

image

输入描述

image

输出描述

image

输入样例

1
5 5
7 1 6 6 14
7 2 3 2
1 2
1 4
3 5
5 1
3 1

输出样例

Yes
No
Yes
Yes
Yes


题意

给定n个数am个询问,n个数呈链状排列,其中n-1条边每个边都有边权b,每经过一个点可以将这个点ai的所有素因子标记,一条边的b如果被标记了,则两个点认为是联通,点可以向左或向右走,现在给m次询问,每次询问是否两个点可以联通

题解

如果能求出每个点出发能够到达的最远的范围,那么就可以用O(1)回答所有询问。
首先,我们从后向前预处理出每个点只向右走能够到的最远范围,采取递归跳跃的方法,均摊复杂度O(1).
然后从前向后求每个点能够到达的最大范围即可

  • 如果i可以走到i+1
    • 如果i+1只向右走的范围里,有让i+1能够返回i的素因子,那么i和i+1的左右拓展区间相等
    • 如果不能回去,那么L[i]=i
  • 如果i走不到i+1
    • 直接暴力向左向右反复横跳,复杂度都是均摊O(1)的

关于判断序列中l到r的范围内是否存在素因子的方法有很多,这里采用的是对于没一个质因子维护一个vector,来存出素因子出现的所有ai的为止,在vector上二分
复杂度: O(nlogn)

代码

#include<bits/stdc++.h>
#define re register
#define ll long long
#define inc(i,j,k) for(re int i=j;i<=k;i++)
#define dec(i,j,k) for(re int i=j;i>=k;i--)
using namespace std;
const int maxn=200010;
inline int read()
{
	re int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
	while(ch>='0'&&ch<='9') {x=x*10+(ch^48); ch=getchar();}
	return x*f;
}
int T,n,m;
int a[maxn];
int b[maxn],l[maxn],r[maxn],low[maxn];
bool vis[maxn];
const int N=200000;
bool notprime[N+5];
int pri[N],tot;
void prim()
{
	memset(notprime,0,sizeof(notprime));
	notprime[0]=notprime[1]=1; tot=0;
	inc(i,2,N)
	{
		if(!notprime[i])
		{
			pri[++tot]=i;	
			low[i]=i;
		}
		for(re int j=1; j<=tot && i*pri[j]<=N; j++)
		{
			notprime[i*pri[j]]=1;
			low[i*pri[j]]=pri[j];
			if(!(i%pri[j])) break;
		}
	}	
}
inline void deal(int x)
{
	inc(i,1,tot)
	{
		if(pri[i]>x) break;
		if(x%pri[i]==0)
		{
			vis[pri[i]]=1;
			while(x%pri[i]==0)
			{
				x/=pri[i];
			}
		}
	}
}
inline void work(int x,int y)
{
	memset(vis,0,sizeof(vis));
	if(x<y)
	{
		int tmp=x;
		deal(a[x]);
		while(tmp>1 && vis[b[tmp-1]])
		{
			tmp--;
			deal(a[tmp]);
		}
		inc(i,x,y)
		{
			if(i==y) 
			{
				printf("Yes\n");
				return;
			}
			deal(a[i]);
			if(!vis[b[i]])
			{
				printf("No\n");
				return;
			}
			
		}
	}else
	{
		int tmp=x;
		deal(a[x]);
		while(tmp<n && vis[b[tmp]])
		{
			tmp++;
			deal(a[tmp]);
		}
		dec(i,x,y)
		{
			if(i==y) 
			{
				printf("Yes\n");
				return;
			}
			deal(a[i]);
			if(!vis[b[i-1]])
			{
				printf("No\n");
				return;
			}
			
		}
	}
	
}
vector <int> primpos[maxn];
//primpos数组每行矢量内元素单调递增
int clean[maxn],tim;
inline void initPrimePos()
{
	inc(i,1,n)
	{
		int x=a[i];
		while(x>1)
		{
			int nowprim=low[x];
			while(nowprim==low[x])
			{
				x/=nowprim;
			}
			if(clean[nowprim]!=tim)
			{
				primpos[nowprim].clear();
				clean[nowprim]=tim;
			}
			primpos[nowprim].push_back(i);
		}
	}
	++tim;
}
inline bool check(int p,int l,int r)
//查询a数组从l到r是否出现了p这个质数
{
	if(primpos[p].size()==0 || primpos[p].back() < l) return 0;
	int tmp = *lower_bound(primpos[p].begin(),primpos[p].end(),l);
	return (tmp<=r);
}
int main()
{
	//freopen("1005.in","r",stdin);
	//freopen("myout.out","w",stdout);
	prim();
	T=read();
	inc(zzt,1,T)
	{
		n=read(); m=read();
		inc(i,1,n) a[i]=read();
		inc(i,1,n-1) b[i]=read();
		initPrimePos();
		dec(i,n,1)
		{
			r[i]=i;
			while(r[i]<n && check(b[r[i]],i,r[i]))
			{
				r[i]=r[r[i]+1];
			}
		}
		//inc(i,1,n) cout<<r[i]<<" "; cout<<endl;
		inc(i,1,n)
		{
			if(i-1>=1 && r[i-1]>=i)
			{
				//如果i-1只向右跳能到达i
				if(check(b[i-1],i,r[i]))
				{
					//如果从i开始只向右跳之后能回到i-1,那么两个情况答案相同
					l[i]=l[i-1];
					r[i]=r[i-1];
				}else{
					//尽了全力也没办法回去,所以i最左端只能到i
					l[i]=i;
				}
			}else
			{
				//i-1只靠向右跳根本拓展不到i,那就先试试向左,在向右在向左无限跳
				l[i]=i;
				while(1)
				{
					bool zzt=0;
					while(r[i]<n && check(b[r[i]],l[i],r[i]))
					{
						zzt=1;
						r[i]=r[r[i]+1];
					}
					while(l[i]>1 && check(b[l[i]-1],l[i],r[i]))
					{
						zzt=1;
						l[i]=l[l[i]-1];
					}
					if(!zzt) break;
				}
			}
		}
		inc(i,1,m)
		{
			re int xx,yy;
			xx=read(); yy=read();
			if(l[xx]<=yy && r[xx]>=yy)
			{
				puts("Yes");
			}else puts("No");
		}
	}
}
posted @   ZzTzZ  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示