容斥原理基础例题(HDU 2204, HDU 3208, HDU 1796)

HDU 2204

题目

求[1, N] ( 1 ≤ N ≤ 1 0 18 ) (1 \le N\le 10^{18}) (1N1018)之间能被表示成 m k m^k mk的数的数量。

容斥思想

1 0 18 约 等 于 2 64 10^{18} 约等于 2^{64} 1018264
预处理质数:int prime[20] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59};
因为合数的话肯定已经包括在所在质因子的部分答案中。
∣ A 2 ∣ |A_2| A2:能被表示成 m 2 m^2 m2的数
∣ A 3 ∣ |A_3| A3:能被表示成 m 3 m^3 m3的数
∣ A 5 ∣ |A_5| A5:能被表示成 m 5 m^5 m5的数
……
∣ A 59 ∣ |A_{59}| A59:能被表示成 m 59 m^{59} m59的数

S = ∣ A 2 ∣ + ∣ A 3 ∣ + ⋯ + ∣ A 59 ∣ − ∣ A 2 ⋂ A 3 ∣ − ∣ A 2 ⋂ A 5 ∣ − ⋯ + ∣ A 2 ⋂ A 3 ⋂ A 5 ∣ + ⋯ S=|A_2|+|A_3|+\cdots+|A_{59}|-|A_2\bigcap A_3|-|A_2\bigcap A_5|-\cdots+|A_2\bigcap A_3\bigcap A_5|+\cdots S=A2+A3++A59A2A3A2A5+A2A3A5+

为什么知道3个集合的交就结束:

因为最多是64:2 * 3 * 5 = 30,2 * 3 * 5 * 7 = 210 > 64不可能有4个相交的情况

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

typedef long long llong;
int prime[20] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59};

llong solve(llong x)
{
	llong ans = 0;
	llong tmp;
	for (int i = 0; i < 17; i++)
	{
		tmp = (llong)(pow(x, 1.0 / prime[i]));
		if (tmp < 2)
			break;
		ans += (tmp - 1);		//delete 1
		for (int j = i + 1; j < 17; j++)
			{
				tmp = (llong) (pow(x, 1.0 / (prime[i] * prime[j])));
				if (tmp < 2)
					break;
				ans -= (tmp - 1);
				for (int k = j + 1; k < 17; k++)
				{
					tmp = (llong) (pow(x, 1.0/(prime[i] * prime[j] * prime[k])));
					if (tmp < 2)
						break;
					ans += (tmp - 1);
				}
			}

	}
	return ans;
}

int main()
{
	llong n;
	while (scanf("%lld", &n) != EOF)
	{
		llong ans = solve(n);
		printf("%lld\n", ans + 1);
	}
	return 0;
}

HDU 3208

题目

定义一个数对答案的权值贡献为这个数能被表示成某个数的乘方的最大的指数。
比如: 9 = 3 2 − − − − 2 9=3^2 ----2 9=322
64 = 8 2 = 2 6 − − − − 6 64=8^2=2^6 ----6 64=82=266
输入 a , b ( 2 ≤ a ≤ b ≤ 1 0 18 ) a,b(2\le a\le b \le 10^{18}) a,b(2ab1018) ,求这个区间的权值和。

容斥思想

∣ A 1 ∣ |A_1| A1:能被表示成 m 1 m^1 m1的数的数量
∣ A 2 ∣ |A_2| A2:能被表示成 m 2 m^2 m2的数的数量
∣ A 3 ∣ |A_3| A3:能被表示成 m 3 m^3 m3的数的数量
∣ A 4 ∣ |A_4| A4:能被表示成 m 4 m^4 m4的数的数量
∣ A 5 ∣ |A_5| A5:能被表示成 m 5 m^5 m5的数的数量
……
∣ A 64 ∣ |A_{64}| A64:能被表示成 m 64 m^{64} m64的数的数量

∣ A i ∣ |A_i| Ai:pow((double)n, 1.0 / i)

∣ B 1 ∣ |B_1| B1:只(最多)能被表示成 m 1 m^1 m1的数的数量
∣ B 2 ∣ |B_2| B2:只(最多)能被表示成 m 2 m^2 m2的数的数量
∣ B 3 ∣ |B_3| B3:只(最多)能被表示成 m 3 m^3 m3的数的数量
∣ B 4 ∣ |B_4| B4:只(最多)能被表示成 m 4 m^4 m4的数的数量
∣ B 5 ∣ |B_5| B5:只(最多)能被表示成 m 5 m^5 m5的数的数量
……
∣ B 64 ∣ |B_{64}| B64:只(最多)能被表示成 m 64 m^{64} m64的数的数量
最多的概念表示乘方的指数最大。

如果用 ∣ A i ∣ |A_i| Ai求出 ∣ B i ∣ |B_i| Bi

举个栗子:
$|B_6| = |A_6| - |A_2|-|A_3| $
∣ B 9 ∣ = ∣ A 9 ∣ − ∣ A 3 ∣ |B_9| = |A_9|-|A_3| B9=A9A3
……
∣ B i ∣ = ∣ A i ∣ − ∑ ∣ A j ∣    ( i ∣ j ) |B_i| = |A_i| - \sum|A_j| \; (i | j) Bi=AiAj(ij)

代码提示

dp[i]:就是上面的Ai,最后结果Bi也是dp[i]

//total表示第一个小于等于n的数没有数可以被表示成m^total的数
for (int i = total - 1; i >= 1; i--)
		for (int j = 1; j < i; j++)
			if (i % j == 0)
				dp[j] -= dp[i];

注意点

这道题坑很多,需要高精度的pow,在这里不写了,不是我想说的重点,不过也是get了一个新技能吧……

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;

typedef long long llong;
const double eps = 1e-8;
const llong inf = (llong)(1e18) + 500;
const llong INF = ((llong)1 << 31);
llong dp[70];

llong fast_pow(llong e, llong n)
{
	llong ans = 1;
	while (n)
	{
		if (n & 1)
			{
				double judge = 1.0 * inf / ans;
				//printf("judge=%lf, %d\n", judge, e > judge);
				if (e > judge)
					return -1;
				ans *= e;
			}
        n >>= 1;
        //printf("e=%lld INF=%lld %d\n", e, INF, e > INF);
        //printf("ans=%lld\n", ans);
		if (e > INF && n > 0)
			return -1;
		e *= e;
	}
	return ans;
}

llong rooting(llong x, int p)
{
	llong e = (llong)(pow((double)x, 1.0 / p) + eps);
	//printf("e=%lld\n", e);
	llong y = fast_pow(e, p);
//printf("y=%lld\n", y);
	if (y == x)
		return e;
	if (y == -1 || y > x)
		return e - 1;

	y = fast_pow(e + 1, p);
	if (y <= x && y != -1)
		return e + 1;
	else
		return e;
}

llong solve(llong x)
{
	memset(dp, 0, sizeof(dp));
	int total;
	dp[1] = x;
	for (int i = 2; i < 64; i++)
	{
		dp[i] = rooting(x, i) - 1;
		//printf("dp[%d]=%lld\n", i, dp[i]);
		if (dp[i] < 1)
			{
				total = i;
				break;
			}
	}
//printf("total = %d\n", total);
	for (int i = total - 1; i >= 1; i--)
		for (int j = 1; j < i; j++)
			if (i % j == 0)
				dp[j] -= dp[i];

	llong ans = 0;
	for (int i = 1; i < total; i++)
		ans += i * dp[i];
	return ans;
}

int main()
{
	llong a, b;
	while (scanf("%lld %lld", &a, &b) != EOF && (a || b))
	{
	    //printf("%lld\n", solve(b));
		printf("%lld\n", solve(b) - solve(a - 1));
	}
	return 0;
}

HDU 1796

题目

Now you get a number N, and a M-integers set, you should find out how many integers which are small than N, that they can divided exactly by any integers in the set. For example, N=12, and M-integer set is {2,3}, so there is another set {2,3,4,6,8,9,10}, all the integers of the set can be divided exactly by 2 or 3. As a result, you just output the number 7.
即求小于N的数能被M中集合整除的数有多少个……

懒得翻译了……就直接复制了

容斥思想

M = { 4 , 6 , 7 } M=\{4,6,7\} M={4,6,7}, N = 31
∣ A 4 ∣ |A_4| A4:能被2整除的数的数量(4, 8, 12, 16, 20, 24, 28)
∣ A 6 ∣ |A_6| A6:能被3整除的数的数量(6, 12, 18, 24, 30)
∣ A 7 ∣ |A_7| A7:能被7整除的数的数量(7, 14, 21, 28)

所以答案为(4, 8, 12, 16, 20, 24, 28, 6, 18, 30, 7, 14, 21, 28)
即:
∣ A 4 ∣ + ∣ A 6 ∣ + ∣ A 7 ∣ − ∣ A 12 ∣ − ∣ A 28 ∣ − ∣ A 42 ∣ + ∣ A 84 ∣ |A_4| +|A_6|+|A_7|-|A_{12}|-|A_{28}| - |A_{42}| + |A_{84}| A4+A6+A7A12A28A42+A84
而12, 28, 42为2个数的lcm,84为3个数的lcm.(例子举残了……)

∣ A 1 ∣ |A_1| A1: 能被1个集合里的数整除的数的数量
∣ A 2 ∣ |A_2| A2: 能同时被2个集合里的数整除的数的数量(即能被2个数的lcm整除)
∣ A 3 ∣ |A_3| A3: 能同时被3个集合里的数整除的数的数量(即能被3个数的lcm整除)

S = ∣ A 1 ∣ − ∣ A 2 ∣ + ∣ A 3 ∣ − ∣ A 4 ∣ + ⋯ S=|A_1|-|A_2|+|A_3|-|A_4|+\cdots S=A1A2+A3A4+
(奇数为+,偶数为-)

代码提示

我比较喜欢位运算枚举

llong ans = 0;
int total = (1 << x);

for (int i = 1; i < total; i++)
{
    int num = 0;
    int tmp = 1;
    for (int j = 0; j < x; j++)
    {
        if ((i & (1 << j)) != 0)
        {
            num++;
            tmp = lcm(tmp, a[j]);
        }
    }
    if (num & 1)
        ans += (n - 1) / tmp;
    else
        ans -= (n - 1) / tmp;
}

注意点

M中集合可能为0,所以如果是0直接不处理不放进a数组中……因为0不能整除任何数,对答案贡献肯定为0

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;

typedef long long llong;
int a[25];
int vis[25];

int gcd(int a, int b)
{
    if (b == 0)
        return a;
    return gcd(b, a % b);
}

int lcm(int a, int b)
{
    return a / gcd(a, b) * b;
}

int main()
{
	int n, m;
	while (scanf("%d %d", &n, &m) != EOF)
	{
		memset(vis, 0, sizeof(vis));

		for (int i = 0; i < m; i++)
			{
				scanf("%d", &a[i]);
			}

        int x = 0;
        for (int i = 0; i < m; i++)
            if (a[i] != 0)
            a[x++] = a[i];

		llong ans = 0;
		int total = (1 << x);
		//printf("total = %d\n", total);
		for (int i = 1; i < total; i++)
        {
            int num = 0;
            int tmp = 1;
            for (int j = 0; j < x; j++)
            {
                if ((i & (1 << j)) != 0)
                {
                    num++;
                    tmp = lcm(tmp, a[j]);
                }
            }

            if (num & 1)
                ans += (n - 1) / tmp;
            else
                ans -= (n - 1) / tmp;

        }

        printf("%lld\n", ans);

	}
	return 0;
}

应用

求 [ 1, m ]区间里和 n 互质的数有几个?

  • if m == n 解为phi(n)

  • 对于任意的m:
    互质的数等于总共的数减去不互质的数

如何求不互质的数?
质因数分解 n, 将质因数保存进数组中(共pos个[0- pos-1])
∣ A 1 ∣ |A_1| A1:能被1个质因数整除的数量
∣ A 2 ∣ |A_2| A2:能被2个质因数整除的数量
……
∣ A p o s ∣ |A_{pos}| Apos:能被pos个质因数整除的数量

a n s = ∣ A 1 ∣ − ∣ A 2 ∣ + ∣ A 3 ∣ − ⋯ ans = |A_1|-|A_2|+|A_3|-\cdots ans=A1A2+A3

代码

int divide(int n)
{
	int pos = 0;
	for (int i = 2; i * i <= n; i++)
	{
		if (n % i == 0)
		{
			p[pos++] = i;
			while (n % i == 0)
				n /= i;
		}
	}
	if (n != 1)
		p[pos++] = n;
    return pos;
}

int solve(int n)
{
	int pos = divide(n);
	int total = (1 << pos);
	int res = 0;

	for (int i = 1; i < total; i++)
	{
		int num = 0;
		int tmp = 1;
		for (int j = 0; j < pos; j++)
		{
			if ((i & (1 << j)) != 0)
			{
				num++;
				tmp *= p[j];
			}
		}

		if (num & 1)
			res += x / tmp;
		else
			res -= x / tmp;
	}
	return x - res;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值