●BZOJ 1853 [Scoi2010]幸运数字
题链:
http://www.lydsy.com/JudgeOnline/problem.php?id=1853
题解:
容斥原理,暴力搜索,剪枝(这剪枝剪得真玄学)
首先容易发现,幸运号码不超过 2*(2^10) 个
然后考虑用容斥原理去得出所有近似幸运号码的个数,
即加上由单数个幸运号码的 LCM得到的近似幸运号码的个数,
然后减去由偶数个幸运号码的 LCM得到的近似幸运号码的个数,
(那个个数就是 N/LCM 嘛)。
实现方法是 DFS。
(DFS就是枚举每个幸运号码选或不选,并对答案加加减减就好了)
但显然会超时,22048 的复杂度,蛤。
优化如下:
1).去掉幸运号码里的倍数。
即如果存在两个幸运号码 i,j,且 i%j==0,那么就不要 i了。
(然后就只剩下 943个了)
2).把幸运号码从大到小枚举。这样在搜索是可以在更靠近搜索树根的位置把剪枝减掉。
3).DFS时发现当前选的数的 LCM>边界,就return掉。
然后就可以玄学地过了。
至于复杂度分析,除了感觉很快,额、、、、
看了一位博主的分析:
如果把剩下的幸运号码看成两两互质的,那么 LCM就等于选的数相乘,
那么显然 sqrt(N)以上的数只能选一个,而小于sqrt(N)的数又只有 15个左右,
所以就很快了。
代码:
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define ll long long #define filein(x) freopen(#x".in","r",stdin); #define fileout(x) freopen(#x".out","w",stdout); using namespace std; bool mark[3000]; ll luck[3000],l,r,ans; int cnt; bool fg; ll gcd(ll a,ll b){ while(b^=a^=b^=a%=b); return a; } void dfs(int p,int num,ll val,const ll &lim){ if(!p) return; ll LCM=val/gcd(val,luck[p])*luck[p]; if(0<LCM&&LCM<=lim) ans+=1ll*lim/LCM*((num+1)%2?1:-1),dfs(p-1,num+1,LCM,lim); dfs(p-1,num,val,lim); } ll solve(ll lim){ ans=0; dfs(cnt,0,1,lim); return ans; } void pre(int dep,ll val){ if(val) luck[++cnt]=val; if(!dep) return; pre(dep-1,val*10+6); pre(dep-1,val*10+8); } int main() { filein(luckynumber); fileout(luckynumber); pre(10,0); int ccnt=0; sort(luck+1,luck+cnt+1); for(int i=1;i<=cnt;i++) for(int j=1;j<i;j++) if(luck[i]%luck[j]==0) mark[i]=1; for(int i=1;i<=cnt;i++) if(!mark[i]) luck[++ccnt]=luck[i]; cnt=ccnt; scanf("%lld%lld",&l,&r); printf("%lld",solve(r)-solve(l-1)); return 0; }
Do not go gentle into that good night.
Rage, rage against the dying of the light.
————Dylan Thomas