【洛谷5610】[Ynoi2013] 大学(并查集)
大致题意: 给定一个序列,支持两种操作:把一个区间中\(x\)的倍数都除以\(x\);询问区间和。
\(O(\sqrt n)\rightarrow O(logn)\rightarrow O(\alpha(n))\)
一道并不难的\(Ynoi\),然而因为各种原因写了一个多小时。。。
最初,看到\(Ynoi\),本能反应就是根号,于是就写了个愚蠢的根号做法,毫无悬念地\(T\)了。
然后,想到可以使用\(set\),结果没想到被卡得和根号做法同分。
最后,无可奈何去参考题解,才发现可以使用并查集,终于过了此题。(尽管预处理的复杂仍然是\(O(n\sqrt V)\)的,询问的复杂度也依旧是\(O(nlogn)\))
解题思路
首先,一个暴力的思路,我们考虑对于每一个数\(x\),开个\(set\)存储所有是其倍数的元素的下标。
每次我们只要到\(x\)的\(set\)中找到在修改区间内的下标\(k\),枚举\(a_k\)原先的因数判断哪些因数不再是它的因数,并在这些因数对应的\(set\)中删去\(k\)。
考虑到一个数每被操作一次,至少会变成原先的一半,因此最多被操作\(log V\)次,但每一次操作还要枚举因数,复杂度难以接受。
然后我们发现,其实每次修改没必要立刻把\(k\)从不再是\(a_k\)因数的元素的\(set\)中删去,只需在每次访问到\(a_k\)时判断其是否为当前操作数\(x\)的倍数即可,若不是则将其从\(set\)中删去。
虽然复杂度已经得到极大优化,但仍旧难以接受。考虑到这里的\(set\)只有删除,而没有加入操作,完全可以用\(vector+\)并查集替代。
具体地,对于每一个\(vector\)(原\(set\))开一个并查集,每当删去一个元素,就在并查集上将它和后一个元素合并,具体实现详见代码。
最后讲讲区间求和操作,由于我们把区间修改转化成了单点修改,直接开树状数组维护即可。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define V 500000
#define pb push_back
using namespace std;
int n,a[N+5],C[V+5];vector<int> P[V+5],f[V+5];
struct TreeArray//树状数组
{
long long a[N+5];I void U(RI x,CI v) {W(x<=n) a[x]+=v,x+=x&-x;}
I long long Q(RI x,long long t=0) {W(x) t+=a[x],x-=x&-x;return t;}
}A;
I int fa(CI v,CI x) {return ~f[v][x]?f[v][x]=fa(v,f[v][x]):x;}//并查集
int main()
{
#define PB(x,i) (P[x].pb(i),f[x].pb(-1))//往vector中加入元素,同时扩展并查集大小
RI Qt,i,j;for(scanf("%d%d",&n,&Qt),i=1;i<=n;++i)//读入
{
scanf("%d",a+i),A.U(i,a[i]),PB(a[i],i);
for(j=2;j*j<=a[i];++j) !(a[i]%j)&&(PB(j,i),j^(a[i]/j)&&(PB(a[i]/j,i),0));//枚举因数
}
for(i=1;i<=V;++i) C[i]=P[i].size(),f[i].pb(-1);
RI op,sz,k;long long l,r,x,t=0;W(Qt--)
{
scanf("%d%lld%lld",&op,&l,&r),l^=t,r^=t;//强制在线
if(op==2) {printf("%lld\n",t=A.Q(r)-A.Q(l-1));continue;}//询问
if(scanf("%lld",&x),(x^=t)==1) continue;//x=1忽略
#define G(x,v) (lower_bound(P[x].begin(),P[x].end(),v)-P[x].begin())//二分
for(i=fa(x,G(x,l));i^C[x]&&(k=P[x][i])<=r;i=fa(x,i+1))//枚举vector中在修改区间内的位置
a[k]%x?(f[x][i]=fa(x,i+1),0):(A.U(k,a[k]/x-a[k]),a[k]/=x);//如果不是x的倍数就删去,否则将其除以x
}return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒