首页 > 代码库 > 【bzoj1853】 Scoi2010—幸运数字

【bzoj1853】 Scoi2010—幸运数字

http://www.lydsy.com/JudgeOnline/problem.php?id=1853 (题目链接)

今天考试考了容斥,结果空知道结论却不会写= =

题意:求区间中不含6,8两个数字及由6,8组成的数字的倍数的的数有几个

Solution 
  容斥原理。 
   先把所有的幸运数字都蒯到一个数组里,将两两之间可以整除的数只留下一个小的。 
   接下来如果暴力组合统计答案的话肯定会TLE,因为就算去掉了可以被整除的数以后还是有1000多个幸运数组。我们考虑dfs,x记录当前已经枚举到了第几个数,y记录已经选了几个数,z表示这几个数的最小公倍数。从大往小枚举,然后加个剪枝,这个神奇的剪枝就直接把复杂度大大降低了,其实就是当最小公倍数大于上界r时返回= =。 
   为什么会这样呢,我想了下。只有当前位数T比较大的情况下,T位幸运数字才会比较多,而当T比较大的情况下,两个幸运数字的lcm(最小公倍数)就会很大,很有可能超越上界,所以这个剪枝是很有效的。

代码:

// bzoj1853#include<algorithm>#include<iostream>#include<cstring>#include<cstdlib>#include<cstdio>#include<cmath>#include<map>#define inf 2147483640#define LL long long#define free(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout);using namespace std;inline LL getint() {    LL x=0,f=1;char ch=getchar();    while (ch>‘9‘ || ch<‘0‘) {if (ch==‘-‘) f=-1;ch=getchar();}    while (ch>=‘0‘ && ch<=‘9‘) {x=x*10+ch-‘0‘;ch=getchar();}    return x*f;}int n,m,vis[100010];LL l,r,ans,a[100010],b[100010];void pre(int x,LL y) {    if (y>r)return;    if (x>0) a[++m]=y;    pre(x+1,y*10+6);    pre(x+1,y*10+8);}LL gcd(LL x,LL y) {    return x%y==0?y:gcd(y,x%y);}void dfs(int x,int y,LL z) {    if (x>n) {        if (y&1) ans+=r/z-(l-1)/z;        else if (y) ans-=r/z-(l-1)/z;        return;    }    dfs(x+1,y,z);    LL tmp=z/gcd(a[x],z);    if ((double)a[x]*tmp<=r) dfs(x+1,y+1,a[x]*tmp);}int main() {    scanf("%lld%lld",&l,&r);    pre(0,0);    sort(a+1,a+1+m);    memset(vis,0,sizeof(vis));    for (int i=1;i<=m;i++) if (!vis[i]) {            for (int j=i+1;j<=m;j++)                if (a[j]%a[i]==0) vis[j]=1;            b[++n]=a[i];        }    for (int i=1;i<=n;i++) a[n-i+1]=b[i];    dfs(1,0,1);    printf("%lld",ans);    return 0;}

  

【bzoj1853】 Scoi2010—幸运数字