1、八
【问题描述】
八是个很有趣的数字啊。八=发,八八=爸爸,88=拜拜。当然最有趣的还是8用二进制表示是1000。怎么样,有趣吧。当然题目和这些都没有关系。
某个人很无聊,他想找出[a,b]中能被8整除却不能被其他一些数整除的数。
【输入文件】
输入文件eight.in。第一行一个数n,代表不能被整除的数的个数。第二行n个数,中间用空格隔开。第三行两个数a,b,中间一个空格。
【输出文件】
输出文件eight.out应包含一个整数,为[a,b]间能被8整除却不能被那n个数整数的数的个数。
【样例输入】
3
77646082 462
216653442
【样例输出】
6378
【数据规模】
对于30%的数据,1≤n≤5,1≤a≤b≤100000。
对于100%的数据,1≤n≤15,1≤a≤b≤10^9,N个数全都小于等于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;
}