【JZOJ 省选模拟】数字收藏(数字收藏)

Description
小 H 是一个收藏家,他喜欢收藏正整数。小 H 有一个习惯,那就是在他睡觉之前,计算在
他收藏的所有正整数中,有多少对正整数的最大公因数恰好是 k。
小 H 每一天可能会新收藏一个正整数,当然,也可能因为某些原因丢弃一个正整数。这使
得他收藏的正整数在不断变化,每天睡前计算出来的值也可能不一样。不过 k 是永远不会变的。
同时,小 H 保证,k 是 1 或质数。
小 H 想知道,在他每新收藏一个正整数,或丢弃一个正整数之后,还有多少对正整数的最
大公因数是 k 呢?

Input
第一行两个整数 n,k。
接下来 n 行,每行两个整数 a,b。
• 如果 a = 0,表示小 H 丢弃了一个他收藏的正整数 b。如果此时小 H 收藏的正整数中没有
b,那可能是小 H 记错了,因此不需要作出任何改变。
• 如果 a = 1,表示小 H 新收藏了一个正整数 b。注意,小 H 可以收藏很多个相同的正整数。

Output
输出 n 行,每行一个整数,表示一次丢弃或者收藏之后的答案。

Sample Input
【样例 1 输入】
5 2
1 2
1 4
0 2
1 2
1 2
【样例 2 输入】
8 3
1 3
0 3
0 3
1 3
1 6
1 9
1 12
1 2

Sample Output
【样例 1 输出】
0
1
0
1
3
【样例 2 输出】
0
0
0
0
1
3
5
5

Data Constraint
记 z 为小 H 收藏过的正整数中的最大值。
对于所有数据,1 ≤ k ≤ z。
【JZOJ 省选模拟】数字收藏(数字收藏)_第1张图片

思路

将每个数/=k,忽略%k!=0的数
考虑容斥原理,那么每新加入一个数 b,答案将增加

【JZOJ 省选模拟】数字收藏(数字收藏)_第2张图片
其中 cnt[d] 表示目前集合里有多少个数是 d 的倍数。

代码

#include
#define ll long long
using namespace std;
const int N = 2e5 + 5;
vector<int> g[N];
int n,k,cnt[N],a[N],m = 100000,miu[N];
ll ans;
bool bz[N];

ll c2(ll x)
{
	return x * (x - 1) / 2;
}

void add(int x)
{
	if(x % k) return;
	a[x]++;
	x /= k;
	int len = g[x].size(),i;
	for(int i = 0; i < len; i++)
	{
		int y = g[x][i];
		ans -= miu[y] * c2(cnt[y]);
		cnt[y]++;
		ans += miu[y] * c2(cnt[y]);
	}
}

void del(int x)
{
	if(x % k) return;
	if(!a[x]) return;
	a[x]--;
	x /= k;
	int len = g[x].size(),i;
	for(int i = 0; i < len; i++)
	{
		int y = g[x][i];
		ans -= miu[y] * c2(cnt[y]);
		cnt[y]--;
		ans += miu[y] * c2(cnt[y]);
	}
}

int main()
{
	freopen("number.in","r",stdin); freopen("number.out","w",stdout);
	int op,x;
	for(int i = 1; i <= m; i++) for(int j = i; j <= m; j += i) g[j].push_back(i);
	for(int i = 1; i <= m; i++) miu[i] = 1;
	for(int i = 2; i <= m; i++)
	if(!bz[i])
	{
		miu[i] = -1;
		for(int j = 2 * i; j <= m; j += i)
		{
			bz[j] = 1;
			if(j / i % i == 0) miu[j] = 0;
			else miu[j] *= -1;
		}
	}
	scanf("%d%d",&n,&k);
	while(n--)
	{
		scanf("%d%d",&op,&x);
		if(op == 0) del(x);
		else add(x);
		printf("%lld\n",ans);
	}
}

你可能感兴趣的:(【JZOJ 省选模拟】数字收藏(数字收藏))