burnside & polya 第二彈 poj四題

 

  一個白天就耗在這麼幾道題中,表示效率太低了=_=。

   今天天氣悶死了, 熱還比較可以忍受,可是悶就實在無法忍受,下午的時候感覺骨頭都要散掉了=_=! 被最後一道題虐了一下午=_=!

   poj 1286

  給一個長度爲n的環染色, 只有三種顏色,考慮旋轉同構和翻轉同構, n<= 24

  其實置換方式只有2*n種,且明顯構成置換羣, 原本想直接枚舉置換,用polya定理,裸找出循環節數即可,後來經cwx的提醒,其實可以手工算出置換的循環節數。

  旋轉x個的置換,其循環節數位gcd(n,x), n爲奇數時,沿對稱軸翻轉的置換,循環節數爲n/2+1, n爲偶數是,n/2的翻轉置換,其循環節數位n/2, 另外n/2的循環節數位n/2+1.

  那麼代碼會變得相當簡單! 看來polya的題“人類智慧”是很重要的,即使可以用計算機裸做,手算一下也會有不一樣的驚喜。

  code:

# include <cstdlib>
# include <cstdio>
# include <cmath>

using namespace std;

typedef long long int64;

int n, i;
int64 ans;

int64 mul(int x, int y)
{
	int64 ask = 1;
	while (y--) ask = ask * x;
	return ask;
}
int gcd(int x, int y) {return !x? y: gcd(y%x, x);}
int main()
{
	freopen("1286.in", "r", stdin);
	freopen("1286.out", "w", stdout);
	for (;scanf("%d", &n), n!= -1;)
	{
		if (n == 0) printf("0\n");
		else if (n & 1)
		{
			ans = mul(3, n/2+1) * n;
			for (i = 1; i <= n; i++)
			  ans += mul(3, gcd(i, n));
			ans /= 2*n;
			printf("%I64d\n", ans);
		}
		else 
		{
			ans = mul(3, n/2) * n/2;
			ans += mul(3, n/2+1) * n/2;
			for (i = 1; i <= n; i++)
			  ans += mul(3, gcd(i, n));
			ans /= 2*n;
			printf("%I64d\n", ans);
		}
	}
	return 0;
}

  poj2409 

  和上題如出一轍,只是顏色數是改變的,方法和上面是一模一樣的。

   code:

  

# include <cstdlib>
# include <cstdio>
# include <cmath>

using namespace std;

typedef long long int64;

int c, s, i;
int64 ans;

int64 mul(int x, int y)
{
	int64 ask = 1;
	while (y--) ask = ask * x;
	return ask;
}
int gcd(int x, int y) {return !x? y: gcd(y%x, x);}
int main()
{
	freopen("2409.in", "r", stdin);
	freopen("2409.out", "w", stdout);
	for (;scanf("%d%d", &c, &s), s!= 0;)
	{
		if (s & 1)
		{
			ans = mul(c, s/2+1) * s;
			for (i = 1; i <= s; i++)
			  ans += mul(c, gcd(i, s));
			ans /= 2*s;
			printf("%I64d\n", ans);
		}
		else 
		{
			ans = mul(c, s/2) * s/2;
			ans += mul(c, s/2+1) * s/2;
			for (i = 1; i <= s; i++)
			  ans += mul(c, gcd(i, s));
			ans /= 2*s;
			printf("%I64d\n", ans);
		}
	}
	return 0;
}

poj2154:

  長度爲n的環,染n種顏色。不過這次只考慮旋轉(不考慮沿對稱軸翻轉)引起的同構! 

  n = 10^9 而且好心的給了3500組測試數據。

   

  和上面的式子是一樣的,我們需要求sum(i^gcd(n,i)) for i in [1, n]

  即使使用快速冪都避免不了tle的命運。

  其實所有的gcd(n,i)的種類是不多的,只有n的約數級別, 一個數的約數是什麼級別的? 我直接的平均是O(logn),應該不會超過O(sqrt(n))

   而如何求出gcd(n,i) = k 有多少個i滿足條件? 很容易想到是phi(n/i)。

   可以通過弄出n的所有質因數,用一遍dfs得到所有n的約數的phi。

   這樣求循環節數的複雜度就降低到了O(sqrt(n)), 然後乘上O(logn)的快速冪,勉勉強強可以過。

 // polya中存在除法操作,但是由於mod的數不是質數,不過可以通過分子的式子中的指數全-1即可。

# include <cstdlib>
# include <cstdio>
# include <cmath>

using namespace std;

typedef long long int64;

const int sqN = 100000+10;
int n, mo;
int tot, phi[sqN], pri[sqN];
bool step[sqN];
int Div[sqN], dphi[sqN], num[sqN], have[sqN];
int64 ans;
void prepare_prime()
{
	int i, j;
	phi[1] = 1; 
	for (i = 2; i <= 100000; i++)
	{
		if (!step[i])
		  step[pri[++tot] = i] = 1, phi[i] = i-1;
		for (j = 1; j <= tot; j++)
		{
		  if (i*pri[j] > 100000) break;
		  step[i*pri[j]] = 1;
		  if (i % pri[j] == 0)
		  {
			  phi[i*pri[j]] = phi[i] * pri[j];
			  break;
		  }
		  else phi[i*pri[j]] = phi[i] * (pri[j]-1);
		}
	}
}
void get_prime()
{
	int i, it;
	for (i = 1; i <= have[0]; i++) have[i] = num[i] = 0;
	have[0] = 0; it = n;
	for (i = 1; i <= tot && pri[i] <= n; i++)
	if (!(n % pri[i]))
	{
	  have[++have[0]] = pri[i];
	  while (!(it % pri[i])) it/= pri[i], num[have[0]]++;
	}
	if (it != 1) have[++have[0]] = it, num[have[0]]++;
}
void dfs(int it, int now, int fai)
{
	int i, fn, fp;
	if (it == have[0]+1)
	{
		Div[++Div[0]] = now; dphi[Div[0]] = fai;
		return;
	}
	dfs(it+1, now, fai);
	fn = now*have[it]; fp = fai*(have[it]-1);
	dfs(it+1, fn, fp);
	for (i = 2; i <= num[it]; i++)
	{
	  fn*= have[it]; fp*= have[it];
	  dfs(it+1, fn, fp);
	}
}
int64 mul(int64 x, int y)
{
	int64 ask = 1;
	for (;y;y>>=1, x= x*x%mo)
	  if (y&1) ask = ask*x%mo;
	return ask;
}
int main()
{
	int i, task;
	freopen("2154.in", "r", stdin);
	freopen("2154.out", "w", stdout);
	prepare_prime();
	scanf("%d", &task);
	while (task--)
	{
		ans = 0;
		scanf("%d %d", &n, &mo);
		for (i = 1; i <= Div[0]; i++) Div[i] = 0; Div[0] = 0;
		get_prime();
		dfs(1, 1, 1);
		for (i = 1; i <= Div[0]; i++)
          ans =  (ans + mul((int64)n,n/Div[i]-1)* dphi[i] % mo) % mo;
		printf("%d\n", (int)ans);  
  	}
	return 0;
}

  poj2888

  和上題的差不多,n仍然爲10^9, 顏色數減到了10, 但是顏色出現了限制—— 給定(x,y)這樣的二元組,表示x顏色和y顏色不能相鄰!

   那麼不能直接使用polya了。

   burnside是可以不管塗染顏色的限制的,但是我們得把所有的塗染方案枚出來麼?這不可能。

   其實我們只需要求出burnside中每個置換的長度爲1的循環數即可。

   熟悉hnoi2008 cards 的同學可以發現,我們仍然可以使用相同方法的dp來求:對於每個置換,找出它的所有循環,把循環內部塗上同一種顏色的方案數就是burnside中針對方案的置換的1長度的循環數,當然,裸dp是不現實的,不過這樣的dp優化應該是矩乘乘法的經典問題了。

    那麼同上,枚舉n的每個因數,配合歐拉函數和矩陣乘法,運用burnside定理計算。

   這裏的除法不能搞上面的猥瑣了,不過mod的數是prime了! 好爽哦,可以直接用除法的逆元了!

  code (速度好慢~~~)

# include <cstdlib>
# include <cstdio>
# include <cmath>
# include <cstring>

using namespace std;

const int mo = 9973, sqN = 100000+100, M = 15;

bool step[sqN];
int  have[sqN], pri[sqN], phi[sqN], divi[sqN], dphi[sqN], num[sqN];
int f[M][M], g[M][M], tmp[M][M];
bool can[M][M];
int tot, n, m, k, ans;
void prepare_prime()
{
  int i, j;
  phi[1] = 1;
  for (i = 2; i <= 100000; i++)
  {
    if (!step[i]) 
      pri[++tot] = i, phi[i] = i-1;
    for (j = 1; j <= tot; j++)
    {
      if (pri[j]*i > 100000) break;
      step[pri[j]*i] = true;
      if (i % pri[j] == 0)
      {
	phi[i*pri[j]] = phi[i] * pri[j];
        break;   
      }
      else phi[i*pri[j]] = phi[i] * (pri[j] - 1);
    }
  }
}
void get_prime()
{
  int i;
  //memset(have, 0, sizeof(have));
  //memset(num, 0, sizeof(num));
  //memset(divi, 0, sizeof(divi)); 
  for (i = 1; i <= have[0]; i++) have[i] = num[i] = 0;
  for (i = 1; i <= divi[0]; i++) divi[i] = 0;
  have[0] = divi[0] = 0; 
  int it = n; 
  for (i = 1; i <= tot && pri[i] <= n; i++)
  if (n % pri[i]==0)
  {
    have[++have[0]] = pri[i];
    while (it % pri[i] == 0) it /= pri[i], num[have[0]]++;
  }
  if (it != 1) have[++have[0]] = it, num[have[0]] = 1;
}

void dfs(int it, int now, int fai)
{
  int tn, tf, i;
  if (it == have[0]+1)
  {
    divi[++divi[0]] = now;
    dphi[divi[0]] = fai;
    return;
  }
  dfs(it+1, now, fai);
  tn = now * have[it]; tf = fai * (have[it]-1);
  dfs(it+1, tn, tf);
  for (i = 2; i <= num[it]; i++)
  {
    tn *= have[it]; tf *= have[it];
    dfs(it+1, tn, tf);
  }
}

void mul(int c[M][M], int a[M][M], int b[M][M])
{
  int i, j, k;
  memset(tmp, 0, sizeof(tmp));
  for (i = 1; i <= m; i++)
    for (j = 1; j <= m; j++)
      for (k = 1; k <= m; k++)
        (tmp[i][j] += a[i][k]*b[k][j]);
  //memcpy(c, tmp, sizeof(tmp));
  for (i = 1; i <= m; i++)
    for (j = 1; j <= m; j++)
      c[i][j] = tmp[i][j]%mo;
}

int ask(int Q)
{
  int i, j;
  for (i = 1; i <= m; i++)
    for (j = 1; j <= m; j++)
      f[i][j] = g[i][j] = can[i][j];
  for (Q--;Q;Q>>=1, mul(g, g, g))
    if (Q & 1) mul(f, f, g);
  int ask = 0;
  for (i = 1; i <= m; i++)
    ask = (ask + f[i][i]) % mo;
  return ask;
}
int pow(int x, int y)
{
  int ask = 1;
  for (;y;y>>=1, x=x*x% mo)
   if (y & 1) ask = ask*x%mo;
  return ask;
}
int main()
{
  int i, task, x, y;
  freopen("2888.in", "r", stdin);
  freopen("2888.out", "w", stdout);
  prepare_prime();
  scanf("%d", &task);
  while (task--)
  {
     ans = 0;
     scanf("%d%d%d", &n, &m, &k);
     memset(can, true, sizeof(can));
     for (i = 1; i <= k; i++)
       scanf("%d%d", &x, &y), can[x][y]=can[y][x]=0;
     get_prime();
      dfs(1, 1, 1);
      for (i = 1; i <= divi[0]; i++)
       ans = (ans + dphi[i]%mo * ask(n/divi[i])) % mo;
       ans = ans *pow(n% mo, mo-2) % mo;
     printf("%d\n", ans);
   }
   return 0;
}


   

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章