UVA11610 【Reverse Prime】
阿新 • • 發佈:2020-08-27
思考
這一題,它的第一個要求是找出所有 \(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
- 做題時,要找出樹狀陣列的基本模型,判斷樹狀陣列維護什麼,需要幾個維護
- 在查詢時,要學會使用二分,事半功倍
(其實主要還是樹狀陣列,只不過加了一個質數篩,二分和一些操作而已)
*/