1. 程式人生 > >另類容斥和尤拉函式巧妙應用(HDU--5514)

另類容斥和尤拉函式巧妙應用(HDU--5514)

There are mm stones lying on a circle, and nn frogs are jumping over them.  The stones are numbered from 00 to m−1m−1 and the frogs are numbered from 11 to nn. The ii-th frog can jump over exactly aiai stones in a single step, which means from stone j mod mj mod m to stone (j+ai) mod m(j+ai) mod m (since all stones lie on a circle).  All frogs start their jump at stone 00, then each of them can jump as many steps as he wants. A frog will occupy a stone when he reach it, and he will keep jumping to occupy as much stones as possible. A stone is still considered ``occupied" after a frog jumped away.  They would like to know which stones can be occupied by at least one of them. Since there may be too many stones, the frogs only want to know the sum of those stones' identifiers.

Input

There are multiple test cases (no more than 20), and the first line contains an integer t,  meaning the total number of test cases.  For each test case, the first line contains two positive integer nn and mm - the number of frogs and stones respectively (1≤n≤10^4, 1≤m≤10^9).  The second line contains nn integers a1,a2,⋯,ana1,a2,⋯,an, where aiai denotes step length of the ii-th frog (1≤ai≤109)(1≤ai≤10^9).

Output

For each test case, you should print first the identifier of the test case and then the sum of all occupied stones' identifiers.

Sample Input

3
2 12
9 10
3 60
22 33 66
9 96
81 40 48 32 64 16 96 42 72

Sample Output

Case #1: 42
Case #2: 1170
Case #3: 1872

本題的大體意思很好搞懂,就是青蛙有一個初始位置0,還有每次能夠跳多少步,青蛙跳的位置是一個圓形的長度已知,我們需要求青蛙能夠落到的位置和。

簡單的分析第一個樣例:

青蛙每次跳9個位置,圓形跑道長為12。則青蛙的能到達的位置為 9、9*2%12=6、9*3%12=3、9*4%12=0、9**5%12=9......迴圈往復。

青蛙每次跳10個位置,那青蛙能到達的位置為10、10*2%12=8、10*3%12=6、10*4%12=4、10*5%12=2、10*6%12=0、10*7%12=10....迴圈往復。

去掉重複的部分則我們知道青蛙可以到達2、3、4、6、8、9、10,答案是42。

通過觀察我們知道青蛙每次跳的步數與圓形跑道的長度求一下gcd,只要gcd倍數的位置青蛙都可以跳到。然後我們可以通過等差數列求和公式算出青蛙所有能跳到的點的位置和。但是這樣我們會重複計算一部分,例如gcd為2和gcd為3的都會把gcd為6算上,這樣就等於把gcd為6的算了兩遍。所以我們想到用容斥。

資料量可以看出有n個gcd(n<=10^4),我們列舉取或不不取的時間複雜度為2^n.所以我們肯定不能用常規的方法去求解。

這道題我們有兩道寫法,我們先看比較傳統的吧。

容斥:

我們能夠知道每個數的gcd都是他的因子。對於每一個因子我們只需要統計一下它被計算了多少次。我們先給那些因子是所求gcd的倍數的出現次數設為1。然後對所有因子進行容斥。我們記錄兩個變數,分別為它需要出現的次數和它已經出現的次數。然後通過兩個做差可以知道對於這個因子需要出現的次數。對於一個gcd確定的數列,我們可以將其縮小gcd倍使其變為1、2、3、4、5........,然後通過等差數列求和如下:((m-1)/gcd)*((m-1)/gcd+1))*gcd/2。

具體程式碼如下:

#include<bits/stdc++.h>
const int maxn = 1e4+5;
typedef long long ll;
using namespace std;
ll gcd(ll a,ll b)
{
	if(b==0) return a;
	return gcd(b,a%b);
}
ll g[maxn],yy[maxn],app[maxn],v[maxn];
int main()
{
	int t,cnt,cas;
	ll n,m,val,res;
	bool ok;
	scanf("%d",&t);
	cas=1;
	while(t--)
	{
		memset(app,0,sizeof(app));
		memset(v,0,sizeof(v));
		cnt=0;
		res=0;
		ok = false;
		scanf("%lld %lld",&n,&m);
		for(int i=1;i*i<=m;i++)
		{
			if(i*i==m) yy[cnt++] = i;
			else if(m%i==0)
			{
				yy[cnt++] = i;
				yy[cnt++] = m/i;
			} 
		}
		sort(yy,yy+cnt);
		cnt--;
		for(int i=0;i<n;i++)
		{
		 scanf("%lld",&val);
		 g[i]=gcd(m,val);
		 if(g[i]==1)
		 {
		 	ok = true;
		 	res = m*(m-1)/2;
		 }
		  for(int j=0;j<cnt;j++)
		   if(yy[j]%g[i]==0)
		   v[j]=1;
	    }
	   // for(int i=0;i<cnt;i++)
	   // printf("%lld\n",v[i]);
	    if(!ok)
	    {
	    for(int i=0;i<cnt;i++)
	    {
	    	if(v[i]!=app[i])
	    	{
	    		ll tt = (m-1)/yy[i];
	    		res+=tt*(tt+1)*yy[i]/2*(v[i]-app[i]);
	    	}
	    	for(int j=i+1;j<cnt;j++)
	    	{
	    		if(yy[j]%yy[i]==0) app[j]+=v[i]-app[i];
	    	}
	    //	printf("%d %lld %lld\n",i,v[i],app[i]);
	    }
	    }
	    printf("Case #%d: %lld\n",cas++,res);
	}
	return 0;
}

還有一個方案是通過尤拉函式巧妙地解決這類問題

例如第一個樣例:

我們對12進行因子分解:12的因子有 2、3、4、6.

我們可以發現  這兩隻青蛙可以跳的分別為2和3的倍數。

我們記錄下m的那些因子是2和3的倍數。在12以內我們可以將其分成:  

  2、10

  3、9

  4、8

  6      這四組數。我們可以通過phi(x)*x/2求出x範圍內和x互質數的和。第一組因為第一組和m的gcd為2所以我們可以看成為6之內的和6互質的數的和,在將其的和擴大gcd倍,phi(6)*6/2 *2 = 12.第二組所有數和m的gcd為3.所以我們可以將其看成4以內的和4互質的和。在將其擴大gcd倍,phi(4)*4/2*3=12.第三組所有數和m的gcd為4,所以我們將其看成3以內和3互質的的和。在將其擴大gcd倍。phi(3)*3/2*4=12. 第四組與m的gcd為6,我們可以將其看作2內的所有數與2互質的數之和。phi(2)*2/2*6=6.

所有值加和則為正確答案。

我們可以看出化簡後的規律為:  例如gcd為g的組,我們可以將其寫作phi(m/g)*(m/g)/2*g.

程式碼如下:

#include<bits/stdc++.h>
const int maxn = 1e4+5;
typedef long long ll;
using namespace std;
ll gcd(ll a,ll b)
{
	if(b==0) return a;
	return gcd(b,a%b);
}
ll getphi(ll x)
{
	ll phi =x;
	for(int i=2;i*i<=x;i++){
		if(x%i==0){
			while(x%i==0) x/=i;
			phi = phi/i*(i-1);
		}
	}
	if(x>1) {
	phi=phi/x*(x-1);
    }
	return phi;

}
ll g[maxn],yy[maxn];
int main()
{
	int t,cnt,cas;
	ll n,m,val,res;
	bool ok;
	scanf("%d",&t);
	cas=1;
	while(t--)
	{
		cnt=0;
		res=0;
		ok = false;
		scanf("%lld %lld",&n,&m);
		for(int i=2;i*i<=m;i++)
		{
			if(i*i==m) yy[cnt++] = i;
			else if(m%i==0)
			{
				yy[cnt++] = i;
				yy[cnt++] = m/i;
			} 
		}
	    for(int i=0;i<n;i++)
	    {
	    	scanf("%lld",&val);
	    	g[i]=gcd(val,m);
	    	if(g[i]==1)
	    	{
	    	 ok=true;
	    	 res=m*(m-1)/2;
	        }
	    }
	    if(!ok)
	    {
	    sort(g,g+n);
	    for(int i=0;i<cnt;i++)
	    {
	    	for(int j=0;j<n;j++)
	    	if(yy[i]%g[j]==0)
	    	{
	    	 res+=getphi(m/yy[i])*m/2;
	    	 break;
	        }
	    }
	    }
	    printf("Case #%d: %lld\n",cas++,res);
	}
	return 0;
}