【学习笔记】[Ynoi2013] 大学

因为学习的其他人的算法,所以叫学习笔记。。。

题意 :

维护序列,支持下列两个操作 :

把区间 [ l , r ] [l,r] [l,r] 中所有 x x x 的倍数除以 x x x
查询区间 [ l , r ] [l,r] [l,r] 的和。
强制在线。

n ≤ 1 0 5 , a ≤ 5 × 1 0 5 n\leq 10^5,a\leq 5\times 10^5 n105,a5×105 ,时限 0.5s \texttt{0.5s} 0.5s

Solution:

伞兵题目 。

求和操作完全就是在误导往线段树上去想 (我太菜了)。

下面做法摘自 command_block

显然,只会有 O ( n log ⁡ a ) O(n\log a) O(nloga) 次生效的除法操作,我们使用树状数组维护区间和,这部分复杂度为 O ( n log ⁡ a log ⁡ n ) O(n\log a\log n) O(nlogalogn)

现在难点在于如何找到应该被除的数。

S d S_d Sd 为序列中所有为 d d d 的倍数的数的集合, S 1... a S_{1...a} S1...a 的大小总和是 ∑ d ( a ) = O ( n d ( a ) ) \sum d(a)=O(nd(a)) d(a)=O(nd(a)) 的。而 d ( a ) m a x = 200 d(a)_{max}=200 d(a)max=200

当我们区间除 d d d 时,只需要在 S d S_d Sd 中查看。

现在问题变为 : 维护一个序列,支持每次删除一个 区间 内的数。

所以… set 暴力?

可以用并查集 + 链表来维护

链表太恶心了,直接并查集查下一个元素

复杂度 O ( n a + n log ⁡ a log ⁡ n ) O(n\sqrt{a}+n\log a\log n) O(na +nlogalogn)

一些技巧:

Step 1 \text{Step 1} Step 1
你发现暴力分解约数是 O ( n a ) O(n\sqrt a) O(na ) 而非 O ( n a 3 ) O(n\sqrt [3] a) O(n3a ) ,会导致复杂度为 O ( n a + n log ⁡ n ) O(n\sqrt a+n\log n) O(na +nlogn),于是改为筛子预处理因数。

复杂度下降至 O ( a ln ⁡ a + n a 3 + n log ⁡ a log ⁡ n ) O(a\ln a+n\sqrt[3]{a}+n\log a\log n) O(alna+n3a +nlogalogn)

Step 2 \text{Step 2} Step 2
预处理因数时,不要用 vector 保存因数,自己写一个邻接表。

甚至发现只有 n < = 1 0 5 n<=10^5 n<=105 个数需要预处理因数,可以大大减小常数。

如果不加上述两条,会得到 98 p t s 98pts 98pts 的好成绩。

Step 3 \text{Step 3} Step 3
发现每一个位置的下标数组和并查集数组的大小是固定的。

所以保存下标和并查集的时候不需要使用 vector,可以自己开一个内存池,用指针来模拟数组。

这一点是非常重要的。

#include <bits/stdc++.h>
#define ll long long
#define lbt(p) ((-p)&(p))
using namespace std;
ll read(){
  ll X=0;char ch=0;
  while(ch<48||ch>57)ch=getchar();
  while(ch>=48&&ch<=57)X=X*10+(ch^48),ch=getchar();
  return X;
}
const int MaxN=100500;
const int MaxA=500500;
const int mxt=6138450+500;
int n;ll t[MaxN];
int head[mxt],nxt[mxt],ver[mxt],tot;
void Add(int x,int y) {
	ver[++tot]=y,nxt[tot]=head[x],head[x]=tot;
} 
void add(int p,int x) {
	while(p<=n) {
	    t[p]+=x; p+=lbt(p);
	}
}
ll qry(int p) {
	ll tot (0);
	while(p) {
		tot+=t[p]; p^=lbt(p);
	}
	return tot;
}
struct node{
	int *f;
	void Init(int n) {
		for(int i=0;i<n;i++) f[i]=i;
	}
	int find(int u) {
		return f[u]==u?u:f[u]=find(f[u]);
	}
}T[MaxA];
int c[MaxA],*p[MaxA],o[MaxN*405],*tp=o,x[MaxN];
void div(int l,int r,int d) {
	if(d==1) return;
	l=lower_bound(p[d],p[d]+c[d],l)-p[d];
	r=upper_bound(p[d],p[d]+c[d],r)-p[d]-1;
	if(l>r) return;
	for(int u=T[d].find(l);u<=r;) {
		int to=p[d][u];
		if(x[to]%d==0) {
			add(to,x[to]/d-x[to]);
			x[to]/=d;
		}
		if(u>=r) break;
		if(x[to]%d) {
			u=T[d].f[u]=T[d].find(u+1);
		}
		else u=T[d].find(u+1);
	}
}
int q,m;
signed main() {
//	freopen("data.in","r",stdin);
//	freopen("own.out","w",stdout);
	n=read(),q=read();
	for(int i=1;i<=n;i++) {
		add(i,x[i]=read());
		m=max(m,x[i]);
		c[x[i]]++;
	}
	for(int i=2;i<=m;i++) {
		if(c[i]) Add(i,i);
		for(int j=i+i;j<=m;j+=i) {
			if(!c[j]) continue;
			c[i]+=c[j];
			Add(j,i);
		}
	}
//	printf("YES\n%d\n",cnt);
	for(int i=2;i<=m;i++) {
		if(c[i]) {
			p[i]=tp; tp+=c[i];
			T[i].f=tp; T[i].Init(c[i]);
			tp+=c[i]; c[i]=0;
		}
	}
	for(int i=1;i<=n;i++) {
		int t=x[i]; if(t<=1) continue;
		for(int j=head[t];j;j=nxt[j]) {
			int d=ver[j];
			p[d][c[d]++]=i;
		}
	}
	ll l,r,x,las=0;
	for(int i=1,op;i<=q;i++){
	    op=read();l=read();r=read();
	    l^=las;r^=las;
	    if(op==1)div(l,r,read()^las);
	    else printf("%lld\n",las=qry(r)-qry(l-1));
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值