【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;
}

  

posted @ 2016-09-27 22:28  MashiroSky  阅读(646)  评论(0编辑  收藏  举报