【八】容斥原理

1、八


【问题描述】

八是个很有趣的数字啊。八=发,八八=爸爸,88=拜拜。当然最有趣的还是8用二进制表示是1000。怎么样,有趣吧。当然题目和这些都没有关系。

某个人很无聊,他想找出[a,b]中能被8整除却不能被其他一些数整除的数。

【输入文件】

输入文件eight.in。第一行一个数n,代表不能被整除的数的个数。第二行n个数,中间用空格隔开。第三行两个数ab,中间一个空格。

【输出文件】

输出文件eight.out应包含一个整数,为[a,b]间能被8整除却不能被那n个数整数的数的个数。

【样例输入】

3

77646082 462

216653442

【样例输出】

6378

【数据规模】

对于30%的数据,1n≤51≤a≤b≤100000

对于100%的数据,1≤n≤151≤a≤b≤10^9N个数全都小于等于10000大于等于1


这道题有点难度。

这就是传说中的容斥原理。

写数学语言有点麻烦,引用wjj的题解。


深搜模块用来枚举所有Si的组合,是奇数就减去,是偶数就加上。

wjj:

#include <stdio.h>
#include <string>
#include <stdlib.h>
#include <bitset>

using std::bitset;

const char fi[] = "eight.in";
const char fo[] = "eight.out";
const int maxN = 20;
const int MAX = 0x7fffff00;
const int MIN = -0x7fffff00;

typedef unsigned long long LLU;
LLU p[1 << maxN];
LLU x[maxN];
LLU a, b, n;

  void init_file()
  {
    freopen(fi, "r", stdin);
    freopen(fo, "w", stdout);
  }

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

  void readdata()
  {
    scanf("%ld", &n);
    for (int i = 0; i < n; ++i)
    {
      scanf("%ld", x + i);
      if (x[i] == 1 || x[i] == 2 || x[i] & 7 == 0)
      {
        printf("0");
        exit(0);
      }
    }
    scanf("%ld%ld", &a, &b);
    --a;
  }
  
  void work()
  {
    int ans = (b >> 3) - (a >> 3);
	//首先賦為|S|。
    p[0] = 1;
	//由於要求公倍數,所以至少是1。
    for (int i = 1; i < (1 << n); ++i)
	//每個可能的01狀態。
    {
      bitset <maxN> s((unsigned long)i); 
      for (int j = 0; j < n; ++j)
       if (s.test(j))
	//找到最後一個被併入的集合。
        {
          LLU pre = p[i ^ (1 << j)];
	//上一狀態的最小公倍數
	//(即j被併入之前的狀態,
	//之前被算出並保存在p數組中)。
          LLU lcm = pre / gcd(pre, x[j]) * x[j];
	//當前狀態的最小公倍數。
          p[i] = lcm <? (b + 1);
	//若這個最小公倍數大於b,
	//則當前集合的元素個數為0,
	//賦為一個以b除商0的值。
          break;
        }
      int lcm = p[i] / gcd(p[i], 8) << 3;
	//每個集合Si都需要與S求交集,
	//再用S的個數減去每個Si與S
	//求得交集的並集的元素個數。
      if ((s.count()) & 1) ans -= b / lcm - a / lcm;
      else ans += b / lcm - a / lcm;
	//ans減去奇加偶減,即奇減偶加
	//當前這個交集的元素個數。
    }
    printf("%ld", ans);
  }
  
int main()
{
  init_file();
  readdata();
  work();
  exit(0);
}

//容斥原理。

//記S = {x| 8|x }∩[a, b];
//Si = {x| ai|x }∩[a, b](i=0, 2, ..., n-1。)
//容易知道|S| = b/8 - (a-1)/8,
//|Si| = b/ai - (a-1)/ai。
//而,Si∩Sj = {x| lcm(ai, aj)|x }∩[a, b]。
//則所求答案為:|S| - |S∩(S0∪S1∪S2∪...∪S(n-1))|。
//根據容斥原理,|union(int i = 0; i < n; ++i) Si|
//= sigma(int i = 0; i < n; ++i) |Si|
//- sigma(0 <= i < j < n) |Si∩Sj|
//+ sigma(0 <= i < j < k < n) |Si∩Sj∩Sk|
//-...-((-1) ^ n)|S1∩S2∩...∩S(n-1)|。

//程序中,用二進制串000...01-111...11來表示
//n個節點取和不取的狀態,用p數組來保存
//每個狀態下所有a值的最小公倍數,
//(即處於1狀態的所有集合的並集)
//枚舉所有的狀態,即可計算出最終結果。



我的:

#include <iostream>
using std::cout;
//using std::cin;
#include <cstdio>
#include <cstdlib>
const long oo = 0x7fff0000;

long n;long a;long b;
long num[15];
long long ans = 0;
bool used[15];

long long gcd(long long a,long long b)
{
	while (b)
	{
		long tmp = b;
		b = a%b;
		a = tmp;
	}
	return a;
}

void dfs(long p,long q)
{
	if (p>n)
	{
		long cnt = 0;
		long long jiaoji = 8;
		for (long i=1;i<n+1;i++)
		{
			if (used[i])
			{
				jiaoji = jiaoji*num[i]/gcd(jiaoji,num[i]);
				if(jiaoji>q)
					break;
				cnt++;
			}
		}
		long tmp = q/jiaoji;
		if (cnt&1==1){ans+=tmp;}else{ans-=tmp;}
		return;
	} 
	used[p] = false;
	dfs(p+1,q);
	used[p] = true;
	dfs(p+1,q);
}

int main()
{
	
	freopen("eight.in","r",stdin);
	freopen("eight.out","w",stdout);
	
	scanf("%ld",&n);
	for (long i=1;i<n+1;i++)
	{
		scanf("%ld",num+i);
	}
	scanf("%ld%ld",&a,&b);
	ans = 0;
	dfs(1,a-1);
	long long ans1 = ans;
	ans = 0;
	dfs(1,b);
	long long ans2 = ans;
	
	printf("%ld",ans1-ans2);
	
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值