【牛客练习赛85】D 数学家的谜题

【牛客练习赛85】D 数学家的谜题

题目简要:

这个问题是这样的,首先给出 \(n\) 个数字,第 \(i\) 个数字为 \(a[i]\)。接下来进行 \(m\) 次操作,每次操作的类型如下:

1:将 \(a[id]\) 的值改为\(x\)

2:令 \(t=a[l]×a[l+1]×...×a[r−1]×a[r]\),求 \(t\) 能被多少个不同的素数整除。


​ 注意到 \(a_i\) 的值域并不大,只有 \(10^5\)。而且题目要求的还是不同的质因子。我们可以先跑一边质数筛看看 \(10^5\) 以内有多少个质数。发现结果其实只有 9000 多个。不到 10000 个。考虑二进制枚举。这就有引出一个黑科技了 bitset。这个东西通过固定的优化使得他的速度不仅是 int\(\dfrac{1}{w}\) 倍,空间也是 \(\dfrac{1}{w}\) 倍(\(w\) 在这里指计算机的位数,我们可以默认为 \(w=32\))。具体使用方法这里不再赘述了,可以自行百度。这种区间查询问题我们第一反应肯定是线段树。那就直接暴力一点,我们建一个线段树,每个节点存一个 bitset。空间大概是相当于开了 \(n\times 4\times10000\times\dfrac{1}{w}\)int。时间复杂度我们下面再讨论。

​ 首先是修改操作,修改是单点修改。我们直接我们找到待修改的点,然后修改他的bitset。分解质因数同样需要 \(\sqrt{x}\) 的时间。单次复杂度大约是 \(O(\sqrt{x}+log(n)\times\dfrac{10000}{w})\)。(\(x\) 表示值域)

​ 查询就是普通的区间查询,对于查询过来的答案我们进行 这个操作。时间大约是 \(O(\log{n}\times\dfrac{10000}{w})\)

​ 总的来说,时间复杂度就是 \(O(n\times\sqrt{x}\;+n\times\log(n)\times\dfrac{10000}{w})\)。这个复杂度我们是完全可以接受的。

代码如下:

#include<bits/stdc++.h>
#define ls(k) (k<<1)
#define rs(k) (k<<1|1)
using namespace std;
const int MAXN = 5e4+5;
int n,m,p[MAXN],a[MAXN];
bool ip[100005];
struct Tree
{
	bitset<10000> v;
}t[MAXN<<2];
bitset<10000> ans;
void pushup(int k)
{
	t[k].v=t[ls(k)].v|t[rs(k)].v;
}
void build(int k,int l,int r)
{
	if(l==r)
	{
		int num=a[l],up=sqrt(a[l])+1;
		for(int i=1;i<=p[0];++i)
		{
			if(p[i]>up||num==1) continue;
			if(num%p[i]==0) t[k].v[i]=1;
			while(num%p[i]==0) num/=p[i];
		}
		if(num!=1)
		{
			int pos=lower_bound(p+1,p+1+p[0],num)-p;
			t[k].v[pos]=1;
		}
		return ;
	}
	int mid=l+r>>1;
	build(ls(k),l,mid);
	build(rs(k),mid+1,r);
	pushup(k);
}
void upd(int pos,int k,int l,int r,int x)
{
	if(l==r&&l==pos)
	{
		t[k].v.reset();
		t[k].v|=ans;
		return ;
	}
	int mid=l+r>>1;
	if(pos<=mid) upd(pos,ls(k),l,mid,x);
	else  upd(pos,rs(k),mid+1,r,x);
	pushup(k);
}
void query(int ll,int rr,int k,int l,int r)
{
	if(ll<=l&&r<=rr)
	{
		ans|=t[k].v;
		return ;
	}
	int mid=l+r>>1;
	if(ll<=mid) query(ll,rr,ls(k),l,mid);
	if(rr>mid)  query(ll,rr,rs(k),mid+1,r);
}
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=2;i<=100000;++i)
	{
		if(p[i]) continue;
		for(int j=2*i;j<=100000;j+=i)
			p[j]=1;
		p[++p[0]]=i;
	}
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	build(1,1,n);
	for(int i=1;i<=m;++i)
	{
		int op;
		scanf("%d",&op);
		if(op==1)
		{
			int id,x;
			scanf("%d %d",&id,&x);
			int num=x,up=sqrt(x)+1;
			ans.reset();
			for(int i=1;i<=p[0];++i)
			{
				if(p[i]>up) continue;
				if(num==1) break;
				if(num%p[i]==0) ans[i]=1;
				while(num%p[i]==0) num/=p[i];
			}
			if(num!=1)
			{
				int pos=lower_bound(p+1,p+1+p[0],num)-p;
				ans[pos]=1;
			}
			upd(id,1,1,n,x);
		}
		else
		{
			int l,r;
			scanf("%d %d",&l,&r);
			ans.reset();
			query(l,r,1,1,n);
			printf("%d\n",ans.count());
		}
	}
	return 0;
}

总结:

​ 主要是 bitset 的用法,这个黑科技的优化还是比较大的,很多题目甚至还可以用 bitset 暴力艹 std。主要是 \(\dfrac{1}{w}\) 很关键,计算时间复杂度时一定要算进去。我就是因为计算错误导致没去写这种写法,想来还是很气的。

posted @ 2021-06-29 17:37  夜空之星  阅读(64)  评论(0编辑  收藏  举报