[BZOJ2393]Cirno的完美算数教室(容斥原理)

题目描述

传送门

题解

此题的数据范围应为 109 .
首先找出[1,r]中所有的baka数,可以计算出只有 21+22+..+29=1023 个。
其次可以发现当存在两个baka数 ab 满足 a|b 的话,那么b是没有价值的,根本不用判断。
所以就把baka数中有因数也是baka数的剔除掉,可以发现只剩下了499个数。
然后可以进行dfs,x,y,lcm分别表示选到第x个数,之前已经选了y个数,所有选的数的lcm。答案=是一个数公倍数的数量-是两个数公倍数的数量+是三个数公倍数的数量…
根据y的奇偶统计答案就行了。
不过这道题非常让我迷惑的一点是时间复杂度。因为这样dfs的话 1010 是跑不出来的。把那449个数打表后可以发现,最小的6个数乘起来已经超过了 109 ,也就是说,在最坏情况下,dfs的时间复杂度应该为 O(C6499)21013 。但是实际上,只有12个数在 103 级别之内,只有26个数在 104 级别之内,也就是说,大部分情况下,选的数的个数不会大于2.我们先忽略不计前38个数,后面的所有的数的搜索复杂度大约应为 O(C2461)105 ;不考虑后面的数,前38个数在最坏情况下复杂度约为 O(C638)3106 ;而考虑将前38个数和后面的组合一下的话,很显然后面的数只有可能选一个,前面的数最多选4个,那么 O(C438C1461)3.5107 .那么这样的话,估算的时间复杂度不会超过 108 .而如果是 1010 的范围的话,合法的数的个数达到了969个,用同样的方法计算明显时间是承受不住的。
那么我们就用这样一种不靠谱的方法证明了这道题的时间复杂度!

代码

#include
#include
#include
#include
using namespace std;
#define LL long long
#define N 10000

LL l,r,a[N],b[N],ans;
bool vis[N];

void get(LL x)
{
    if (x>r) return;
    if (x) a[++a[0]]=x;
    get(x*10+2);
    get(x*10+9);
}
LL gcd(LL a,LL b)
{
    return (!b)?a:gcd(b,a%b);
}
void dfs(int x,int y,LL lcm)
{
    if (x>b[0])
    {
        if (y&1) ans+=r/lcm-(l-1)/lcm;
        else if (y) ans-=r/lcm-(l-1)/lcm;
        return;
    }
    dfs(x+1,y,lcm);
    lcm=(lcm*b[x])/gcd(lcm,b[x]);
    if (lcm<=r) dfs(x+1,y+1,lcm);
}
int main()
{
    scanf("%lld%lld",&l,&r);
    get(0);sort(a+1,a+a[0]+1);
    for (int i=1;i<=a[0];++i)
        if (!vis[i])
        {
            b[++b[0]]=a[i];
            for (int j=i+1;j<=a[0];++j)
                if (a[j]%a[i]==0) vis[j]=1;
        }
    dfs(1,0,1);
    printf("%lld\n",ans);
}

你可能感兴趣的:(题解,容斥原理)