1. 程式人生 > >尤拉函式&莫比烏斯反演

尤拉函式&莫比烏斯反演

近幾天做了幾道有關反演的問題,在此集合一下吧。

1、[BZOJ 2301]HAOI2011 Problem b

2、[BZOJ 2440]中山市選2011 完全平方數

3、gcd

4、[BZOJ 2186]SDOI2008 莎拉公主的困惑

5、[BZOJ 3529]SDOI2014 數表

(蒟蒻自認為反演一類的的題目重要的就是記住兩個重要的公式:1、sigma(mu[i] , i|n ) = [n==1]   2、sigma(phi[i] , i|n ) = n

題解:

1、[BZOJ 2301]HAOI2011 Problem b

這基本上算是入門題了吧。。。。

gcd(a,b)==k等價於gcd(a/k,b/k)==1,這樣我們將範圍除k,等價到求區間內互質的二元組數。

假設query(n,m)得到gcd(a,b)==1的數量,那麼答案很明顯就是gcd(b,d) - gcd(a-1,d) - gcd(b,c-1) + gcd(a-1,c-1)

簡單了,下面簡述一下推導過程吧


為了不被多組資料卡掉,我們觀察到n/k的答案只有不超過根號n個不同答案,對mu[]字首和優化,那麼每次詢問時O(sqrt(n))的複雜度

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
 
using namespace std;
int l,r,ans,mid,INF,K,Case,tot;
int prime[41005],mu[41005],i,j;
bool check[41005];
 
void init(){
  mu[1]=1;
  for (i=2;i<41000;i++){
    if (!check[i]) mu[i]=-1, prime[++tot]=i;
    for (j=1;j<=tot;j++){
      if (i*prime[j]>41000) break;
      check[i*prime[j]]=1;
      if (i%prime[j]==0) {mu[i*prime[j]]=0;break;}
        else mu[i*prime[j]] = -mu[i];
    }
  }
}
 
bool Judge(){
  int ret=0;
  for (int i=1;i*i<=mid;i++)
    ret+=(mid/i/i)*mu[i];
  return (ret>=K);
}
 
int main(){
  //freopen("2440.in","r",stdin);
  //freopen("2440.out","w",stdout);
  init();
  INF=41000; INF=INF*INF;
  scanf("%d",&Case);
  while (Case--){
    scanf("%d",&K);
    l=1; r=INF;
    while (l<=r){
      mid=((long long)l+r)>>1;
      if (Judge()) ans=mid, r=mid-1;
        else l=mid+1;
    }
    printf("%d\n",ans);
  }
  return 0;
}

2、[BZOJ 2440]中山市選2011 完全平方數

好題啊~~~~~~

首先必須想到一個利器——二分!

我們二分出一個數怎樣檢驗有多少個完全平方數比他小呢?

從莫比烏斯函式的定義來看

接下來利用容斥從n中減去為平方數倍數的數。

如果一個數有平方因子,莫比烏斯函式值為0,不必考慮。

如果不為0,實質上就是容斥,減去mu[i]*n/(i*i)即可

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
 
using namespace std;
int l,r,ans,mid,INF,K,Case,tot;
int prime[41005],mu[41005],i,j;
bool check[41005];
 
void init(){
  mu[1]=1;
  for (i=2;i<41000;i++){
    if (!check[i]) mu[i]=-1, prime[++tot]=i;
    for (j=1;j<=tot;j++){
      if (i*prime[j]>41000) break;
      check[i*prime[j]]=1;
      if (i%prime[j]==0) {mu[i*prime[j]]=0;break;}
        else mu[i*prime[j]] = -mu[i];
    }
  }
}
 
bool Judge(){
  int ret=0;
  for (int i=1;i*i<=mid;i++)
    ret+=(mid/i/i)*mu[i];
  return (ret>=K);
}
 
int main(){
  //freopen("2440.in","r",stdin);
  //freopen("2440.out","w",stdout);
  init();
  INF=41000; INF=INF*INF;
  scanf("%d",&Case);
  while (Case--){
    scanf("%d",&K);
    l=1; r=INF;
    while (l<=r){
      mid=((long long)l+r)>>1;
      if (Judge()) ans=mid, r=mid-1;
        else l=mid+1;
    }
    printf("%d\n",ans);
  }
  return 0;
}

3、gcd

(由於網上沒找到原題,就簡述一下題意吧)

給你n個正整數,以及一個正整數k。然後有q個詢問,每次詢問一個x。要求回答從這n個數中選出正好k個數並且使得他們的最大公約數是x的方案數是多少。輸出答案mod 10^9+7值。n,q,x<=10^6,k<=n。時限:5s

還是容斥。。。。。

sum[]記錄數字i的約數有多少個,這樣方便計算。

預處理計算對於每一個x的答案ans[x],用到組合之類的東西。

複雜度大約是nlogn。

詢問直接輸了,具體還是見程式碼吧。

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long LL;
const int Maxn=1000001, Mod=(1e9)+7;;
#define C(n,m) ((LL)jc[n]*ny[m]%Mod*ny[(n)-(m)]%Mod)
int check[Maxn],jc[Maxn],ny[Maxn],mu[Maxn],prime[Maxn];
int sum[Maxn],i,j,tot,x,n,k,Case;
LL ans[Maxn];

int qck(int a,int b){
  int ret=1;
  for (;b>0;b>>=1){
  	if (b&1) ret=(LL)ret*a%Mod;
  	a=(LL)a*a%Mod;
  }
  return ret;
}

void init(){
  mu[1]=1;
  for (i=2;i<Maxn;i++){
  	if (check[i]==0)  { mu[i]=-1;prime[++tot]=i; }
  	for (j=1;j<=tot;j++){
  	  if (prime[j]*i>=Maxn) break;
  	  check[prime[j]*i]=prime[j];
  	  if (i%prime[j]) mu[prime[j]*i]=-mu[i];
  	    else { mu[prime[j]*i]=0;break; }
  	}
  	if (check[i]==0) ny[i]=qck(i,Mod-2);
      else ny[i]=(LL)ny[i/check[i]]*ny[check[i]]%Mod;
  }
  jc[0]=ny[0]=1;
  jc[1]=ny[1]=1;
  for (i=2;i<Maxn;i++){
    jc[i]=(LL)jc[i-1]*i%Mod;
    ny[i]=(LL)ny[i]*ny[i-1]%Mod;
  }
}

int main(){
  freopen("gcd.in","r",stdin);
  freopen("gcd.out","w",stdout);
  init();
  scanf("%d%d",&n,&k);
  for (i=1;i<=n;i++){
    scanf("%d",&x);
    sum[x]++;
  }
  for (i=1;i<Maxn;i++)
    for (j=i+i;j<Maxn;j+=i)
      sum[i]=sum[j]+sum[i];
  for (i=1;i<Maxn;i++)
    for (j=1;i*j<Maxn;j++){
      if (sum[i*j]<k) continue;
      ans[i]=(ans[i]+(LL)mu[j]*C(sum[i*j],k)+Mod)%Mod;
    }
  scanf("%d",&Case);
  while (Case--){
  	scanf("%d",&x);
  	printf("%I64d\n",ans[x]);
  }
  return 0;
}
4、[BZOJ 2186]SDOI2008 莎拉公主的困惑

鳴謝zy給我的啟發!

如果0<x<n,x與n互質,那麼x+n也一定與n互質。

否命題也成立。

那麼我們只有求出在m!中與m!互質的數,也就是phi(m!)。

答案就是n!/m!*phi(m!)。

關於計算答案我用了些什麼線性逆元亂七八糟的東西,理論上覆雜度還可以,實際上被卡常數了,跑得很慢的。

trick:雖然題目上沒說,但是資料上是這麼給的:R是一個比較大的質數(大於所有n,m)

#include <cstdio>
#include <cstring>
#include <algorithm>
 
using namespace std;
typedef long long LL;
const int Maxn=10000001;
int Case,Mod,check[Maxn],prime[Maxn],tot,i,j;
int phi[Maxn],jc[Maxn],ny[Maxn],n,m,l,r,mid,t,ans;
 
int qck(int a,int b){
  int ret=1;
  for (;b>0;b>>=1){
    if (b&1) ret=(LL)ret*a%Mod;
    a=(LL)a*a%Mod;
  }
  return ret;
}
 
void init(){
  jc[0]=1; ny[0]=1;
  jc[1]=1; ny[1]=1;
  for (i=2;i<Maxn;i++){
    if (check[i]==0) prime[++tot]=i;
    for (j=1;j<=tot;j++){
      if (i*prime[j]>=Maxn) break;
      check[i*prime[j]]=prime[j];
      if (i%prime[j]==0) break;
    }
     
    jc[i]=(LL)jc[i-1]*i%Mod;
    if (check[i]==0) ny[i]=qck(i,Mod-2);
      else ny[i]=(LL)ny[check[i]]*ny[i/check[i]]%Mod;
  }
   
  for (i=1,phi[0]=1;i<=tot;i++)
    phi[i]=(LL)phi[i-1]*(prime[i]-1)%Mod*ny[prime[i]]%Mod;
     
  for (i=1;i<Maxn;i++)
    ny[i]=(LL)ny[i-1]*ny[i]%Mod;
   
}
 
int main(){
  //freopen("2186.in","r",stdin);
  //freopen("2186.out","w",stdout); 
  scanf("%d%d",&Case,&Mod);
  init();
  while (Case--){
    scanf("%d%d",&n,&m);
    if (m>n) {printf("0\n");continue;}
    l=1; r=tot; t=0;
    while (l<=r){
      mid=(l+r)>>1;
      if (prime[mid]<=m) t=mid, l=mid+1;
        else r=mid-1;
    }
    ans=(LL)jc[m]*phi[t]%Mod*jc[n]%Mod*ny[m]%Mod;
    printf("%d\n",ans);
  }
  return 0;
}

5、[BZOJ 3529]SDOI2014 數表

(終於到最後一題了睡覺

其實題目裡把a給很大是無用的,算一下表格內最大的數字不會超過50W


以sum[]為關鍵字排序,逐步更新G[],用Problem b的方法分段優化時間,用樹狀陣列維護G[],複雜度大約就是O( q*logn*sqrt(n) )

貼上醜醜的程式碼:

#include <cstdio>
#include <cstring>
#include <algorithm>
 
using namespace std;
#define lowbit(x) ((x)&(-(x)))
int ans[20005],tr[100005],prime[100005];
int i,j,k,kk,N,tot,mu[100005];
bool check[100005];
struct query
{
  int n,m,k,num;
  bool operator <(const query &a)const
    { return k<a.k; }
} q[20005];
 
struct divsum
{
  int x,y;
  bool operator <(const divsum &a)const
    { return y<a.y; }
}sum[100005];
 
void init(){
  for (i=1;i<=100000;i++){
    for (j=1;j*j<=i;j++)
    if (i%j==0){
      sum[i].y+=j;
      if (i/j!=j) sum[i].y+=i/j;
    }
    sum[i].x=i;
  }
  sort(sum+1,sum+100001);
 
  mu[1]=1;
  for (i=2;i<=100000;i++){
    if (check[i]==0) {prime[++tot]=i;mu[i]=-1;}
    for (j=1;j<=tot;j++){
      if (prime[j]*i>100000) break;
      check[prime[j]*i]=1;
      if (i%prime[j]==0) {mu[i*prime[j]]=0;break;}
        else mu[i*prime[j]]=-mu[i];
    }
  }
}
 
void ins(int x,int y){
  for (int i=x;i<=100000;i+=lowbit(i))
    tr[i]+=y;
}
 
int get(int x){
  int ret=0;
  for (int i=x;i>0;i-=lowbit(i))
    ret+=tr[i];
  return ret;
}
 
int get(int l,int r)
{ return get(r)-get(l-1);}
 
int main(){
  //freopen("table.in","r",stdin);
  //freopen("table.out","w",stdout);
  init();
  scanf("%d",&N);
  for (i=1;i<=N;i++){
    scanf("%d%d%d",&q[i].n,&q[i].m,&q[i].k);
    q[i].num=i;
  }
  sort(q+1,q+N+1);
  for (i=1,j=1;i<=N;i++){
    for (;j<=100000 && sum[j].y<=q[i].k;j++)
      for (k=1;k*sum[j].x<=100000;k++)
        ins(k*sum[j].x,sum[j].y*mu[k]);
    for (k=1;k<=q[i].n&&k<=q[i].m;k=kk+1){
      kk=min( q[i].n/(q[i].n/k), q[i].m/(q[i].m/k) );
      ans[q[i].num]+=get(k,kk)*(q[i].n/k)*(q[i].m/k);
    }
    if (ans[q[i].num]<0){
      ans[q[i].num]+=2147483647;
      ans[q[i].num]++;
    }
  }
  for (i=1;i<=N;i++)
    printf("%d\n",ans[i]);
  return 0;
}

——————————————————————————————————————————

最後戳兩個題目,以後做吧:

[bzoj 2154]Crash的數字表格

[bzoj 3434]wc2014時空穿梭