P2567 [SCOI2010]幸运数字
题意:
我们规定只含6或8的数字为幸运号码,而幸运号码的倍数我们也认为是幸运号码,问[l,r]中有多少个幸运号码?
题解:
第一反应是数位dp,但其实不是,我们可以打表观察下,幸运数字其实很少,也就1000多个,所有我门全可以用容斥来去做。
先去搞定所有范围内的合法数字,然后直接容斥
对于有个幸运数字X,在[L,R]中X的倍数的个数,就是R/X-(L-1)/X,但是两个幸运数字有可能会存在交集,即重复计算,所有要用到容斥,选一个幸运数字-选两个幸运数字的lcm+选3个幸运数字的lcm-…(容斥原理)
但是直接这样做也会超时,需要剪枝:
- 对于两个幸运数字a,b,如果b%a==0,那么如果b/x就一定包含了a/x,因此这样的b没有必要,也就是幸运数字的倍数我们就不用管了,这样就剩下943个幸运号码
- 如果当然的lcm大于r,就没必要继续搜索了,因为已经超出范围
- 我们将幸运数字从大到小排序,可以使得lcm更快的超越上届r
代码:
#include<bits/stdc++.h>
#define debug(a,b) printf("%s = %d\n",a,b);
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
clock_t startTime, endTime;
//Fe~Jozky
const ll INF_ll=1e18;
const int INF_int=0x3f3f3f3f;
inline ll read(){ll s=0,w=1ll;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')w=-1ll;ch=getchar();}while(ch>='0'&&ch<='9') s=s*10ll+((ch-'0')*1ll),ch=getchar();//s=(s<<3)+(s<<1)+(ch^48);return s*w;
}
void rd_test(){#ifdef ONLINE_JUDGE#elsestartTime = clock(); //计时开始freopen("2567.in","r",stdin);#endif
}
void Time_test(){#ifdef ONLINE_JUDGE#elseendTime = clock(); //计时结束printf("\n运行时间为:%lfs\n",(double)(endTime - startTime) / CLOCKS_PER_SEC);#endif
}
const int N=20005;
const int dig1=6;
const int dig2=8;
ll l,r,a[N],ans,tot;
inline bool cmp(ll x,ll y){return x>y;}
void init(ll x){if(x>r)return;if(x>0)a[++tot]=x;init(x*10+dig1);init(x*10+dig2);
}
ll gcd(ll x,ll y){return y?gcd(y,x%y):x;
}
void dfs(int now,int sum,ll k){if(now>tot){if(!sum)return;if(sum&1)ans+=(r/k-(l-1)/k);else ans-=(r/k-(l-1)/k);return;}dfs(now+1,sum,k);if((double long)k/gcd(k,a[now])<=(double long)r/a[now])dfs(now+1,sum+1,k*a[now]/gcd(k,a[now]));
}
int main(){scanf("%lld%lld",&l,&r);init(0);sort(a+1,a+tot+1,cmp);int tmp=0;for(int i=1;i<=tot;i++){for(int j=i+1;j<=tot&&a[i];j++){if(!a[j])continue;if(a[i]%a[j]==0)a[i]=0;}if(a[i])a[++tmp]=a[i];}tot=tmp;dfs(1,0,1);printf("%lld\n",ans);return 0;
}