2015年5月31日 星期日

[CF 547C] Mike and Foam

作法:

首先可以把問題化為:支援一個資料結構,可以加入和刪除數,並且詢問目前裡面有幾個數和給定的某個數互質。設當前詢問的數是$x$,其因數由為$d_1,...,d_r$,那麼$x$和所有當前裡面的數的 gcd 值只有可能是$x$的因數,因此設目前總共有$a_i$的數和$x$的 gcd 等於$i$,則只有$x$的因數的$a$值非$0$,而我們想求的就是$a_1$。單一個$a_i$的值不容易求,但如果令$num_i$代表目前這些數裡面有幾個數是$i$的倍數,顯然可以在加入和刪除一個數時維護好$num_i$的陣列,那麼考慮$num_{d_i}$,他其實就會等於所有滿足$d_i | d_j$ 的$a_{d_j}$ 的總和,也就是如果令$A_i=\{ d_j | d_j\%d _i=0 \}$,那麼$\displaystyle num_{d_i}=\sum_{t\in A_i}a_t$,因此現在要用已知的$num_{d_i}$們的式子來求出$a_1$。再來就有點類似莫比烏斯反演的概念了,那東西的詳細可以參考維基,其實和這題一樣本質上來說就是排容原理。不妨設$d_1,...,d_k$就是$x$的所有質因數(不包含$1$),那麼考慮集合$A_1,...,A_k$,跟他們的宇集$\{ d_1,...,d_r \}$,並且以下記$|S|$代表$S$中所有元素的$a$值的和。可以得到$A_1\bigcup A_2\bigcup ... \bigcup A_k$包含了所有$x$的非$1$的因數,因此用宇集扣掉他之後就只剩下$1$,也就是我們要求扣掉之後的集合內元素的權值和(雖然他只有一個元素)。這時就可以用排容了,首先宇集的權值和就是$num_1$,再來要扣掉$|A_1|,...,|A_k|$,由上面的結論可以知道$|A_i|$就是$num_{d_i}$,直接代入即可。再來要加回長的像$|A_i\bigcap A_j|$的這種東西,這裡就是莫比烏斯變換關鍵的地方,因為$d_i$和$d_j$是兩個$x$的不同的質因數,所以他們互質,而回到$A_i$的定義,他裡面蒐集了所有是$d_i$倍數的$x$的因數,所以由$d_i,d_j$互質就可以得到$|A_i\bigcap A_j|=num_{i\cdot j}$,因為他們的交集就代表著滿足他是$d_i,d_j$倍數的$x$的因數,所以是滿足他是$d_i\cdot d_j$倍數的$x$的因數,所以就可以化簡成$num_{i\cdot j}$了。到了好幾個$A$交集在一起時一樣可以融合起來變成一個$num$值,於是這樣就成功求出答案了。

而實作的方法又轉了個彎,因為注意到上面那條式子裡面,我們會用到的$num$值的下標都是「沒有平方因子的數」(square-free),或是說把他質因數分解的話會分成好幾個不同的質數的一次方相乘,而$num$值前面的係數則和當前下標的質因數個數有關,若個數為奇數則為$-1$,若為偶數則為$1$,這東西就是莫比烏斯函數,因此在計算上我們只要先把$x$的所有因數找出來,求出所有因數的$num$值乘上其莫比烏斯函數的值的總和就可以了。

code :



#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn=500000+10 ;
 
int mu[maxn],di[maxn] ;
bool vis[maxn] ;
void getmu()
{
    for(int i=2;i*i<maxn;i++) if(!vis[i])
        for(int j=i*i;j<maxn;j+=i) vis[j]=1 , di[j]=i ;
    for(int i=2;i<maxn;i++)
    {
        if(!vis[i]){mu[i]=-1 ; continue ;}
        mu[i]= (i/di[i])%di[i] ? mu[i/di[i]]*-1 : 0 ;
    }
}
 
int a[maxn],num[maxn] ;
bool on[maxn] ;
vector<int> vd ;
void getdiv(int x)
{
    vd.clear() ;
    for(int i=1;i*i<=x;i++) if(x%i==0)
    {
        vd.push_back(i) ;
        if(i*i!=x) vd.push_back(x/i) ;
    }
}
 
int getval()
{
    int ret=num[1] ;
    for(auto i : vd) ret+=num[i]*mu[i] ;
    return ret ;
}
 
inline void add(int x)
{
    for(auto i : vd) num[i]+=x ;
}
 
main()
{
    getmu() ;
    int n,Q ; scanf("%d%d",&n,&Q) ;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]) ;
    LL ans=0 ;
    while(Q--)
    {
        int x ; scanf("%d",&x) ;
        getdiv(a[x]) ;
        if(!on[x]) ans+=getval() , add(1) , on[x]=1 ;
        else add(-1) , ans-=getval() , on[x]=0 ;
        printf("%I64d\n",ans) ;
    }
}
 

沒有留言:

張貼留言