您的位置:首页 > 其它

[BZOJ 1853][SCOI 2010]幸运数字(容斥原理+DFS)

2015-02-26 10:22 316 查看

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=1853

思路

我们可以先求出[1,r][1,r]范围内形如6…68…86…6这样符合题意的数的升序序列AA,然后对于任意的i<j,Aj=kAii,删去AjA_j,那么我们就能得到一个新的升序数列BB,在这个序列中,没有一个数是另一个数的倍数。

然后很显然就是容斥了,最终的答案=是1个数的倍数的数-是2个数lcm的数+是3个数lcm的数-是4个数lcm的数……

这个可以用DFS来做,DFS时记录当前的lcm是多少,以及是多少个数的lcm,最终枚举完BB序列中所有数是否被计入的情况后,是偶数个数(注意特判0)的lcm就减,是奇数个数的lcm就加,因为在区间[1,x][1,x]中,是lcmlcm的倍数(包含lcmlcm)的数的个数为[xlcm][\frac{x}{lcm}]个,那么当前的在区间[l,r][l,r]中是xxx个数的lcmlcm的数的个数就是[rlcm]−[l−1lcm][\frac{r}{lcm}]-[\frac{l-1}{lcm}]

然后有个剪枝,如果当前的lcm已经超出了rr的范围,很显然它是不在题目给的范围内的,那么就不继续搜了,直接退出搜索。由于如果超出rr的范围的话,lcm的规模大概在101810^18级别之上,用long long int会爆掉数据范围,只能用double先估算一下lcmlcm的规模,然后剪枝,或者直接用unsigned long long int(比前面的方法快100ms左右,但是个人认为不推荐在实际考试时用,因为依然有可能被某些数据爆掉,很危险)

但是尽管这样做仍然会TLE,因为如果从小往大枚举BB序列中的数是否被计入lcm的话,剪枝时间比较晚,优化效果不好,而题目时限为2s,因此要从大往小枚举BB序列,在同样的序列下,从大往小枚举会尽早剪枝,效率很高,极限数据下大概能比优化前的程序快一两秒

代码

1、用double先估算lcm规模

/**************************************************************
Problem: 1853
User: qpswwww
Language: C++
Result: Accepted
Time:736 ms
Memory:1004 kb
****************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 10050

using namespace std;

typedef long long int LL;

LL l,r,ans,a[MAXN],b[MAXN]; //a数组中保存那些是2..29...92..2类型的数,b数组中是去掉了倍数的a数组
bool mark[MAXN]; //mark[i]=true表明a[i]是某些a[j]的倍数,j<i

LL gcd(LL a,LL b)
{
if(b==0) return a;
return gcd(b,a%b);
}

void DFS1(LL x) //当前找到的数字是x
{
if(x>r) return;
a[++a[0]]=x;
DFS1(x*10+6);
DFS1(x*10+8);
}

void DFS2(int pos,LL used,LL nowlcm) //当前DFS到了b[pos],used=已经用了的数字个数,nowlcm是之前用了的数字的lcm
{
if(pos>b[0])
{
if(used&1) ans+=r/nowlcm-(l-1)/nowlcm; //加上是奇数个数公倍数的数量
else if(used) ans-=r/nowlcm-(l-1)/nowlcm; //减去是偶数个数的公倍数的数量
return;
}
DFS2(pos+1,used,nowlcm);
LL newlcm=nowlcm/gcd(nowlcm,b[pos]);
if((double)newlcm*b[pos]>r) return; //剪枝:前pos个数的最小公约数已经超过了r,就不继续DFS了
DFS2(pos+1,used+1,newlcm*b[pos]);
}

int main()
{
scanf("%lld%lld",&l,&r);
DFS1(6);
DFS1(8);
sort(a+1,a+a[0]+1);
for(int i=1;i<=a[0];i++)
if(!mark[i])
{
b[++b[0]]=a[i];
for(int j=i+1;j<=a[0];j++)
if(!(a[j]%a[i])) //a[j]是a[i]的倍数
mark[j]=1;
}
for(int i=1;i<=b[0];i++) a[b[0]-i+1]=b[i];
for(int i=1;i<=b[0];i++) b[i]=a[i];
DFS2(1,0,1);
printf("%lld\n",ans);
return 0;
}


2、直接使用unsigned long long int

/**************************************************************
Problem: 1853
User: qpswwww
Language: C++
Result: Accepted
Time:632 ms
Memory:1004 kb
****************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 10050

using namespace std;

typedef unsigned long long int LL;

LL l,r,ans,a[MAXN],b[MAXN]; //a数组中保存那些是2..29...92..2类型的数,b数组中是去掉了倍数的a数组
bool mark[MAXN]; //mark[i]=true表明a[i]是某些a[j]的倍数,j<i

LL gcd(LL a,LL b)
{
if(b==0) return a;
return gcd(b,a%b);
}

void DFS1(LL x) //当前找到的数字是x
{
if(x>r) return;
a[++a[0]]=x;
DFS1(x*10+6);
DFS1(x*10+8);
}

void DFS2(int pos,LL used,LL nowlcm) //当前DFS到了b[pos],used=已经用了的数字个数,nowlcm是之前用了的数字的lcm
{
if(pos>(int)b[0])
{
if(used&1) ans+=r/nowlcm-(l-1)/nowlcm; //加上是奇数个数公倍数的数量
else if(used) ans-=r/nowlcm-(l-1)/nowlcm; //减去是偶数个数的公倍数的数量
return;
}
DFS2(pos+1,used,nowlcm);
LL newlcm=(nowlcm*b[pos])/gcd(nowlcm,b[pos]);
if(newlcm>r) return; //剪枝:前pos个数的最小公约数已经超过了r,就不继续DFS了
DFS2(pos+1,used+1,newlcm);
}

int main()
{
scanf("%llu%llu",&l,&r);
DFS1(6);
DFS1(8);
sort(a+1,a+a[0]+1);
for(int i=1;i<=(int)a[0];i++)
for(int j=i+1;j<=(int)a[0];j++)
if(a[j]%a[i]==0)
mark[j]=true;
for(int i=1;i<=(int)a[0];i++)
if(!mark[i])
b[++b[0]]=a[i];
for(int i=1;i<=b[0];i++) a[b[0]-i+1]=b[i]; //一个优化
for(int i=1;i<=b[0];i++) b[i]=a[i];
DFS2(1,0,1);
printf("%llu\n",ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: