【CF Hello 2019】莫比乌斯反演+bitset
一道神奇的莫比乌斯反演+bitset
CF HELLO2019f
题意:你有n(n 1e5) 个集合,每个集合中的数字都不超过7000。
操作1:将一个集合改为只有单独你赋给的一个数
操作2:将一个集合改为两个集合的并。
操作3:将一个集合改为两个集合两两gcd。
操作4:求某一个集合中某一个数的出现个数(mod 2)
看到这个mod 2 应该就瞬间想到bitset,但是如果bitset维护值域的话就没办法做第三个操作。
考虑莫比乌斯反演,我们维护集合F[x][y]表示集合x中y的倍数出现次数(y作为因数的原数个数)。
如果这样那么第一个操作赋值为Y就是$ F[d] = [d|Y] $
第二个操作就是两个集合按位加(到二进制里就是异或)
第三个操作就是两个集合按位乘(到二进制里就是与)
第四个操作,由于有(f[n]表示原数列集合),反演一波
$ F[n] = \sum_{n|d}f[d] $
$ f[n] = \sum_{n|d}F[d]*\mu[d/n] $
由于我们已经维护好了F数组,那么我们对每个数都预处理 $ \sum_{n|d}\mu[d/n] $(没有的地方直接补0),之后就和集合与一下就剩下的1个数%2就是答案了。
#include<stdio.h>
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<bitset>
using namespace std;
bitset<7003>MU[7003],NUM[7003],sst[100005];
int mu[7003],pri[7003],cnt;
bool mk[7003];
int n,q;
void oula() {
mk[1] = mk[0] = 1; mu[1] = 1;
for(int i=2;i<=7000;i++) {
if(!mk[i]) { pri[++cnt] = i; mu[i] = -1; }
for(int j=1;j<=cnt&&1ll*i*pri[j]<=7000;j++) {
int k = i*pri[j];
mk[k] = 1;
if(i%pri[j]==0) {
mu[k] = 0;
break;
}
mu[k] = -mu[i];
}
}
}
int main() {
scanf("%d%d",&n,&q);
oula();
for(int i=1;i<=7000;i++) {
for(int j=i;j<=7000;j+=i) {
MU[i][j] = (mu[j/i]!=0);
NUM[j][i] = 1;
}
}
int op,x,y,z;
for(int i=1;i<=q;i++) {
scanf("%d",&op);
if(op==1) {
scanf("%d%d",&x,&y);
sst[x] = NUM[y];
} else if(op==2) {
scanf("%d%d%d",&x,&y,&z);
sst[x] = ( sst[y]^sst[z] );
} else if(op==3) {
scanf("%d%d%d",&x,&y,&z);
sst[x] = ( sst[y]&sst[z] );
} else {
scanf("%d%d",&x,&y);
int o = (sst[x]&MU[y]).count();
printf("%d",o&1);
}
}
}