Sum HDU - 4407(容斥,二进制枚举,逆向思维)

题意:初始化下,给你1到n的n个数字。接下来进行m次操作,每次操作有两种类型,操作1:将求区间[l,r]之间的所有与p互质的数的和,操作2:将x位置上的数改成c。
思路:直接求与p互质的数的和不好求,可以求出所有与p不互质的数的和再用区间和减去这个数。
然后就对p进行质因子分解,并用二进制枚举每次每种可能的与p的公因数,并用容斥的思想和等差数列求和记录每个公因数的在[l,r]范围内贡献。
对于操作2就很好处理了,每次进行操作1时,对所有在[l,r]范围内的修改进行判断加上与当前查询p互质的修改的值,如果被修改前的值也是与p互质的,则要减去。

map<int, int> mp;
vector<ll> v;
void prime(ll p)
{
	v.clear();
	for (ll i = 2;i*i<= p;i++)
	{
		if (p%i == 0)
		{
			while (p%i == 0)
				p /= i;
			v.emplace_back(i);
		}
	}
	if (p > 1)v.emplace_back(p);
}
ll sum(ll l, ll r, ll c) 
{
	ll st, ed, num;
	st = (ll)ceil(l*1.0 / c)*c;
	ed = r / c*c;
	num = r / c - (l - 1) / c;
	return st > ed ? 0 : num * (st + ed) / 2;
}
ll get(ll l,ll r)
{
	int n = v.size();
	ll ans = 0;
	f(i, 1, (1 << n) - 1)
	{
		int cot = 0;
		ll tmp = 1;
		f(j, 0, n - 1)
		{
			if (i>>j&1)
			{
				cot++;
				tmp *= v[j];
			}
		}
		if (cot & 1)ans += sum(l, r, tmp);
		else ans -= sum(l, r, tmp);
	}
	return ans;
}
int main()
{
	//freopen("in.txt", "r", stdin);
	int t;
	cin >> t;
	while (t--)
	{	
		mp.clear();
		int n, m;
		scanf("%d%d", &n, &m);
		ll x, y, op, c;
		while (m--)
		{
			scanf("%lld", &op);
			if (op == 1)
			{
				scanf("%lld%lld%lld", &x, &y, &c);
				if (x > y)swap(x, y);
				prime(c);
				ll ans = (ll)(y - x + 1)*(x + y) / 2;
				ans -= get(x, y);
				for (auto i : mp)
				{
					if (i.first >= x && i.first <= y)
					{
						if (gcd(i.second, c) == 1)ans += i.second;
						if (gcd(i.first, c) == 1)ans -= i.first;
					}
				}
				printf("%lld\n", ans);
			}
			else
			{
				scanf("%lld%lld", &x, &c);
				mp[x] = c;
			}
		}
	}
	return 0;
}

你可能感兴趣的:(基础数论,容斥原理)