鼠标点击网页出现文字特效

UVA11610 【Reverse Prime】

本人看到此题没有翻译,就附带了一个自己的翻译版本


思考

这一题,它的第一个要求是找出所有 \(7\) 位反向质数及其质因数的个数。

我们应该需要质数筛筛选1~\(10^{7}\)的所有数,这里就不慢慢介绍了。但是,重读题,我们突然发现反向质数都是 \(7\) 位,而将它反过来后的数字却是 \(6\) 位数,这就说明了最后一位数肯定是0,这样,我们就可以只筛到\(10^{6}\),也就是说,筛所有反向数,如果是质数,反向过来,末尾填 \(0\) 就是了。这样,我们就减少了时间复杂度,这一部分函数的代码如下:

#define N 1e6+10
void Euler_prime()   //欧拉筛
{

	memset(is_prime,true,sizeof(is_prime));   //先全部标记为是质数  
	is_prime[1]=false;	//特判一下1不是质数
	for(int i=2;i<N;i++)	//枚举2~N的每个数
	{
		if(is_prime[i]) prime[++tot]=i;	//如果是质数
		for(int j=1;j<=tot;j++)	
		{
			if(prime[j]*i>N)
				break;
			is_prime[prime[j]*i]=false;
			if(i%prime[j]==0)   
				break;
		}
	}
}
int solve(int num)  //实现了num数的反向
{
    int len=0,ret=0,bit[20];
    while(num)  
    {
        bit[len++]=num%10;
        num/=10;
    }
    for(int i=0;i<len;i++)
    {
        ret=ret*10+bit[i];
    }       //本函数到这里实现了res=num的反向数
    while(ret<100000) ret*=10;  //因为求出来的数不一定是6位数,所以要加上这行代码,末尾填0
    return ret;
}

这里求完了,接着就要算出它的质因数的个数,这个应该是非常简单的了。但是还要注意,因为我们是筛选的1~\(10^{6}\)的质数,再反过来在末尾填 \(0\) 的,顺序都被打乱了,所以我们要将所有反向质数从小到大排序。这里为了离散化,使用了 \(STL\) 库中的 \(map\),不懂的同学可以自行百度一下。初始化的部分如下:

map<int,int>m;  //为了能够离散化,我使用了map
void init() //初始化函数
{
    Euler_prime();   //先筛了吧……
    for(int i=1;i<=tot;i++)
        a[i]=solve(prime[i]);   //a[i]表示第prime[i]个数的反向质数
    sort(a+1,a+1+tot);  //此题需要从小到大排序,才能在后面的二分中查询
    for(int i=1;i<=tot;i++)
        m[a[i]]=i;  //将其离散化
/*以下函数部分实现了求i的质因数*/
    for(int i=1;i<=tot;i++) 
    {
        fac[i]=2;	//fac[i]用来贮存第i个反向质数的质因数的数量,初值一定要赋2
        int tmp=a[i];
        for(int j=1;j<=tot&&prime[j]*prime[j]<=tmp;j++) 
        {
            while(tmp%prime[j]==0)
            {
                tmp/=prime[j];
                fac[i]++;
            }
        }
        if(tmp>1) fac[i]++; //在特殊情况下,tmp未除尽,质因数的数量要再加一个
    }
}

现在,再来看看题目给我们的 \(2\) 个操作,一个是删除,一个是查询。如果我们用普通的模拟来解决的话,肯定是会超时的。这里,我们就会想到一种特殊的数据结构——树状数组,也就是此题的关键。由于题目的特殊性,这里,我们必须要定义两个树状数组,一个树状数组 \(c1\) 记录当前 \(i\) 节点之前有多少个数,而另一个 \(c2\) 记录了从第一个位置到当前位置反向质数的质因数的个数的总和。在查询时,我们还要注意一下,要找出第 \(0\)\(i\) 的所有的反向质数的质因数的数量的累计总和,我们需要找出它的位置,因此可以用二分来解决这个问题,这时,问题就变得很简单。

树状数组的代码如下:

/**********以下是树状数组基本函数**********/
int lowbit(int k)   //神奇的lowbit函数
{
    return k&-k;
}
void add1(int x,int v)  //
{
    for(int i=x;i<N;i+=lowbit(i))
        c1[i]+=v;
}
void add2(int x,int v)  //
{
    for(int i=x;i<N;i+=lowbit(i))
        c2[i]+=v;
}
ll getsum1(int x)   //
{
    ll ans=0;
    for(int i=x;i>0;i-=lowbit(i))
        ans+=c1[i];
    return ans;
}
ll getsum2(int x)   //
{
    ll ans=0;
    for(int i=x;i>0;i-=lowbit(i))   
        ans+=c2[i];
    return ans;
}
/**********以上是树状数组基本函数**********/
int main()
{
    init(); //初始化......
	char op[2]; //因为一个字符的输入总是有问题,所以就定义字符串啦
    int k;
    for(int i=1;i<=tot;i++)
    {
        add1(i,1);  //存在则压入数字1
        add2(i,fac[i]); //当前位置有多少个反向质数
    }
    while(scanf("%s%d",op,&k)!=EOF) //输入
    {
        if(op[0]=='q')  //既然要输出0~i的所有反向质数,那么我们需要将它先在树状数组中找出来,才好输出总和
        {
            k++;    //因为本人习惯于从1开始,而此题从0开始,只得k++
            int l=1,r=tot,mid;  //既然我们要将这个数在树状数组中找出,那么我们用二分是个不错的选择!
            while(l<=r)
            {
                mid=(l+r)/2;    //用mid变量来搜出k的位置
                ll tmp=getsum1(mid);        //记录一下mid的位置
                if(tmp==k)  //搜出来了就break
                    break;
                else if(tmp<k)  //如果当前的比k小,说明当前数左边的数也肯定也比k小,所以往右找
                    l=mid+1;
                else    //否则如果当前的比k大,说明当前数右边的数也肯定也比k大,所以往左找
                    r=mid-1;
            }
            printf("%lld\n",getsum2(mid));  //最后输出总和就好啦!
        }
        if(op[0]=='d')  
        {
            add1(m[k/10],-1);   //不存在了就减去1,(就是把它删了)
            add2(m[k/10],-fac[m[k/10]]);    //将这个数从第一个位置到当前位置有多少个反向质数表为0(也是把它删了^_^)删数时因为转换的是6位数,所以要这里要/10
        }
    }
	return 0;
}

呼——整篇文章就差不多了,我再把完整代码整合一下:

#include <bits/stdc++.h>
#define N 1000001   //宏定义一个最大值
#define ll long long    //宏定义ll为long long (懒~)
using namespace std;    //命名空间
bool is_prime[N];   //is_prime[i]表示的是i是否为质数,true则是,false则不是
int prime[N],tot=0,a[N],fac[N];  //prime存贮质数,tot存储质数有多少,a[i]记录了prime[i]的反向质数,fac[i]存储的是i的质因数的个数
ll c1[N],c2[N]; //这里要定义两个树状数组,一个树状数组c1记录当前i节点之前有多少个数,而另一个c2记录了从第一个位置到当前位置反向质数的质因数的个数的总和
map<int,int>m;  //为了能够离散化,我使用了map
void Euler_prime()   //欧拉筛
{

	memset(is_prime,true,sizeof(is_prime));     
	is_prime[1]=false;
	for(int i=2;i<N;i++)
	{
		if(is_prime[i]) prime[++tot]=i;
		for(int j=1;j<=tot;j++)
		{
			if(prime[j]*i>N)
				break;
			is_prime[prime[j]*i]=false;
			if(i%prime[j]==0)   
				break;
		}
	}
}
int solve(int num)  //实现了num数的反向
{
    int len=0,ret=0,bit[20];
    while(num)  
    {
        bit[len++]=num%10;
        num/=10;
    }
    for(int i=0;i<len;i++)
    {
        ret=ret*10+bit[i];
    }       //本函数到这里实现了res=num的反向数
    while(ret<100000) ret*=10;  //因为求出来的数不一定是6位数,所以要加上这行代码
    return ret;
}
void init() //初始化函数
{
    Euler_prime();   //先筛了吧……
    for(int i=1;i<=tot;i++)
        a[i]=solve(prime[i]);   //a[i]表示第prime[i]个数的反向质数
    sort(a+1,a+1+tot);  //此题需要排序
    for(int i=1;i<=tot;i++)
        m[a[i]]=i;  //将其离散化
/*以下函数部分实现了求i的质因数*/
    for(int i=1;i<=tot;i++) 
    {
        fac[i]=2;
        int tmp=a[i];
        for(int j=1;j<=tot&&prime[j]*prime[j]<=tmp;j++) 
        {
            while(tmp%prime[j]==0)
            {
                tmp/=prime[j];
                fac[i]++;
            }
        }
        if(tmp>1) fac[i]++; //在特殊情况下,要加一个
    }
}
/**********以下是树状数组基本函数**********/
int lowbit(int k)   //神奇的lowbit函数
{
    return k&-k;
}
void add1(int x,int v)  //
{
    for(int i=x;i<N;i+=lowbit(i))
        c1[i]+=v;
}
void add2(int x,int v)  //
{
    for(int i=x;i<N;i+=lowbit(i))
        c2[i]+=v;
}
ll getsum1(int x)   //
{
    ll ans=0;
    for(int i=x;i>0;i-=lowbit(i))
        ans+=c1[i];
    return ans;
}
ll getsum2(int x)   //
{
    ll ans=0;
    for(int i=x;i>0;i-=lowbit(i))   
        ans+=c2[i];
    return ans;
}
/**********以上是树状数组基本函数**********/
int main()
{
    init(); //初始化......
	char op[2]; //因为一个字符的输入总是有问题,所以就定义字符串啦
    int k;
    for(int i=1;i<=tot;i++)
    {
        add1(i,1);  //存在则压入数字1
        add2(i,fac[i]); //当前位置有多少个反向质数
    }
    while(scanf("%s%d",op,&k)!=EOF) //输入
    {
        if(op[0]=='q')  //既然要输出0~i的所有反向质数,那么我们需要将它先在树状数组中找出来,才好输出总和
        {
            k++;    //因为本人习惯于从1开始,而此题从0开始,只得k++
            int l=1,r=tot,mid;  //既然我们要将这个数在树状数组中找出,那么我们用二分是个不错的选择!
            while(l<=r)
            {
                mid=(l+r)/2;    //用mid变量来搜出k的位置
                ll tmp=getsum1(mid);        //记录一下mid的位置
                if(tmp==k)  //搜出来了就break
                    break;
                else if(tmp<k)  //如果当前的比k小,说明当前数左边的数也肯定也比k小,所以往右找
                    l=mid+1;
                else    //否则如果当前的比k大,说明当前数右边的数也肯定也比k大,所以往左找
                    r=mid-1;
            }
            printf("%lld\n",getsum2(mid));  //最后输出总和就好啦!
        }
        if(op[0]=='d')  
        {
            add1(m[k/10],-1);   //不存在了就减去1,(就是把它删了)
            add2(m[k/10],-fac[m[k/10]]);    //将这个数从第一个位置到当前位置有多少个反向质数表为0(也是把它删了^_^)删数时因为转换的是6位数,所以要这里要/10
        }
    }
	return 0;
}
/*
    这么长的代码终于打完了,呼——
    来总结一下:
    - 看到质数,数量众多,首先想质数筛(推荐欧拉筛)
    - 在资料一对一映射时,为了省时间省财富,我推荐STL的map
    - 做题时,要找出树状数组的基本模型,判断树状数组维护什么,需要几个维护
    - 在查找时,要学会使用二分,事半功倍
    (其实主要还是树状数组,只不过加了一个质数筛,二分和一些操作而已)
*/
posted @ 2020-08-27 20:03  Holy-Glory-Alex  阅读(113)  评论(0编辑  收藏  举报