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