1. 程式人生 > >幸運數字 容斥

幸運數字 容斥

print 例如 們的 AR 預處理 從大到小 dfs gcd IE

洛谷P2567 要點: 很容易考慮到容斥。先預處理出所有幸運數字,加上它們的倍數個,再找所有兩個的最小公倍數減去倍數個,再加上3的…… 至於[a,b],“前綴和”思想處理即可。

但是暴力的考慮復雜度2^2046,tle; 所以要考慮如下剪枝: 1.如果b|a,那麽對於所有的x|b,都有x|a,所以這樣的b是沒有用的。去掉所有的“偽幸運數字”,還有943個。 2.循環時,當這時的LCM已經大於b時,可以直接不要,因為之後若再取LCM,只能更大,都不會滿足要求,回溯。 3.將所有的真幸運數字由大到小排序,可以使得LCM更快的超過b。例如:5,4,3,2,1;假如這次要取5,4,3,2,1的最小公倍數,而b是18,若從大到小,那麽取了5,4就可以回溯了;反之,要取1,2,3,4才能回溯。

至於代碼實現: 1.找幸運數字直接枚舉。去偽的時候,標記一下。(n方復雜度) 2.容斥采用dfs(算容斥時很好用),標記一下取數的個數。奇數的LCM加上倍數個,反之減去。 3.“b-(a-1)”可以直接算,不用循環兩遍。

(4.疑問:為什麽要乘1.0)

解答: 本身kk * num[dep]有可能會爆long long 所以乘1.0變成float型,(float範圍 -3.4 -1e38)~3.4* 1e38)足矣)

(double範圍: -1.7 -1e308)~1.7 1e308) (long double範圍:-1.2 -1e4932~1.2 1e4932)

或者更好的方法是:移項; if(kk<=B/num[dep]) 這樣一定不會爆long long

代碼如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e4+5;
ll A,B;
ll num[N],a[N];
bool mark[N];
ll ans,tot,sum,n;
void dfs1(int dep,int cnt,ll val)
{
    //if(val>b) return;
    if(dep>cnt) return;
    if(dep==cnt)
    {
        a[++tot]=val;
        return
; } dfs1(dep+1,cnt,val*10+6); dfs1(dep+1,cnt,val*10+8); } void sieve() { for(int i=1;i<=tot;i++) { if(!mark[i]) num[++n]=a[i]; for(int j=1;j<=tot;j++) if(a[j]%a[i]==0) mark[j]=1; } } bool cmp(ll a,ll b) { return a>b; } ll gcd(ll a,ll b) { if(a<b) swap(a,b); ll g=b; while(1) { ll k=a%b; if(k==0) break; g=k;a=b;b=g; } return g; } ll zhi(ll t) { return B/t-(A-1)/t; } void dfs2(int dep,int cnt,ll val) { if(val>B) return; if(dep>n) { if(cnt==0) return; ll now=zhi(val); ans+=now*((cnt&1)?1:-1); return; } dfs2(dep+1,cnt,val); ll kk=val/gcd(val,num[dep]); if(1.0*kk*num[dep]<=B) dfs2(dep+1,cnt+1,kk*num[dep]); } int main() { scanf("%lld%lld",&A,&B); for(int i=1;i<=10;i++) dfs1(0,i,0); sieve();//sieve:篩; sort(num+1,num+n+1,cmp); dfs2(1,0,1); printf("%lld",ans); return 0; }

幸運數字 容斥