polya定理应用

本文介绍了Polya定理在解决置换群问题中的应用,包括优化欧拉函数和快速幂运算。通过实例分析了如何利用Polya定理处理不同情况,如poj 2409和2154题,讨论了置换群的最小交换权问题(poj 3270),以及字符串处理中的周期计算(poj 1026)。此外,还涉及了判断字符串是否为某字串平方的问题(poj 3128)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

TT_last 原创,转载请注明。

polya定理不知道可先看潘震皓的 《置换群快速幂运算研究与探讨》,下面是根据论文所做的一些题目。

poj 2409

 很裸的一道polya定理应用题目,

处理的状态有:一个点顺时针旋转 0 ~ n-1 个点

还有就是对称旋转的情况:

当n为偶数时:有两个对应的点相连线 和两条对应线段中点连线 分别形成的对称轴

当n为奇数时:仅有一个点与对应直线形成对称轴。

 

 #include <iostream>
#include <cmath>
using namespace std;
int c,s;
int gcd(int a,int b)
{
    if(b == 0) return a;
    else  return gcd(b,a%b);
}
int polya(int n,int m)
{
    int ans,i;
    __int64 tmp = 0;
    for(i = 0;i < n;i ++)
    {
       tmp += pow(m*1.0,1.0*gcd(n,i)); //n个循环  移动多少位。 
    }
    if(n&1)
    {
      tmp += n*pow(m*1.0,(n+1)/2.0);  //n个循环  一个点与对应线段中点连线 
    }else
    {
      tmp += n/2*pow(m*1.0,n/2+1)+n/2*pow(m*1.0,n/2);  //n个循环  分别为两个点连线 两条相对线段中点连线。
    }
    ans = tmp/(2*n);
    return ans;
}
int main()
{
    while(scanf("%d%d",&c,&s),c|s)
    {
       printf("%d\n",polya(s,c));
    }
    return 0;
}


 

poj 2154

 /*
 欧拉函数 + 快速幂 + 筛选质因数
*/

原本n很大,如果按照前面的做法去做 从1 ~ n,不断枚举,那肯定要tle,这里需要用欧拉函数优化一下。

根据polya定理:方案数(先不考虑取余):

for(i = 0;i < n;i ++)

   ans += pow(n,gcd(n,i));

从中,如果我们提取相同的gcd(n,i) 和取得它们的个数,那么时间复杂度将会减少很多。

设 m = gcd(n,i);m可以肯定是n的因子,而m = gcd(n,i),的个数就是 1 = gcd(n/m,i/m)的i/m个数,

这个可以用欧拉函数来求 euler(n/i);然后再根据polya定理做一下也就可以了。

 

#include <iostream>
using namespace std;
const int maxn = 32000;
int n,m,tmp;
int p[maxn],num,a[400],b[400],ans;
bool sign[maxn];
void init()
{
 int i,j;
 num = 0;
 p[num++] = 2;
 for(i = 4;i < maxn;i += 2) sign[i] = true;
 for(i = 3;i < maxn;i ++)
 {
  if(!sign[i])
  {
   p[num++] = i;
   for(j = i*i;j < maxn;j += i)
    sign[j] = true;
  }
 }
}
void factor(int x)
{
 int i;
 tmp = 0;
 for(i = 0;i < num;i ++)
 {
  if(x < p[i]) break;
  if(x%p[i] == 0)
  {
   b[tmp] = 0;
   while(x%p[i] == 0)
   {
    x /= p[i];
    b[tmp]++;
   }
   a[tmp++] = p[i];
  }
 }
 if(x > 1) b[tmp] = 1,a[tmp++] = x;  
}
int euler(int x)
{
 int ret = x,i;
 for(i = 0;i < num;i ++)
 {
  if(x < p[i]) break;
  if(x%p[i] == 0)
  {
   while(x%p[i] == 0) x /= p[i];
   ret -= ret/p[i];
  }
 }
 if(x > 1) ret -= ret/x;
 return ret;
}
int pow_m(int a,int b)
{
 int ret = 1,t = a%m;
 while(b)
 {
  if(b&1) ret = (ret*t)%m;
  t = (t*t)%m;
  b >>= 1;
 }
 return ret;
}
void dfs(int dep,int sum)
{
 int i,temp;
 if(dep == tmp)
 {
  ans = (ans + (euler(n/sum)%m)*pow_m(n,sum-1))%m;
 }else
 {
  for(i = 0,temp = 1;i <= b[dep];temp*=a[dep],i ++)
   dfs(dep+1,sum*temp); 
 }
}
int main()
{
 int x;
 init();
 scanf("%d",&x);
 while(x--)
 {
  scanf("%d%d",&n,&m);
  factor(n);
  ans = 0;
  dfs(0,1);
  printf("%d\n",ans);
 }
 return 0;
}


 poj 3270

/*置换群 最小交换权
第一种状态:polya循环中最小的那个数 去与其它数据交换。
费用: sum+(len-2)*min2;
第二种状态:从外面拿最小的数进来参与交换,最后换回去:
费用: sum+min2+(len+1)*min;
换进来 1 + 循环 len -1 + 出去 1 = len+1;
min2 出去1 + 进来 1 = 2;
*/

#include <iostream>
#include <algorithm>
using namespace std;
const int inf = 1000000000;
const int maxn = 10005;
int v[maxn],b[maxn],n;
int hash[maxn*10];
bool sign[maxn];
int main()
{
    int ans,min,min2,sum;
    int i,j,t,len;
    while(scanf("%d",&n)!=EOF)
    {
       min = inf;
       for(i = 0;i < n;i ++) 
       {
             scanf("%d",&v[i]);
             b[i] = v[i];
             sign[i] = false;
             if(min > v[i]) min = v[i];
       }
       sort(b,b+n);
       for(i = 0;i < n;i ++) hash[b[i]] = i;
       ans = 0;
       for(i = 0;i < n;i ++)
       {
          if(!sign[i])
          {
             t = i;
    min2 = v[i];
             len = 0;sum = 0;
             sign[i] = true;
             do{
                len ++;
                sum += v[t];
                sign[t] = true;
                if(min2 > v[t]) min2 = v[t];
                t = hash[v[t]];
             }while(t != i);
             int ans1,ans2;
             ans1 = sum+(len-2)*min2;
             ans2 = sum+min2+(len+1)*min;
             ans += ans1 > ans2 ? ans2:ans1;
          }
       }
       printf("%d\n",ans);
    }
    return 0;
}


 

poj 1026

题目是给出一个密钥,然后给出k和一段字符串,然后根据上面的密钥不处理字符串k次。处理的方法是:Character in the message at the position i is written in the encoded message at the position ai, where ai is the corresponding number in the key。

如果直接模拟肯定是tle的,我的做法是,先求出每一个循环,可以肯定每一个循环的周期就是那个循环的长度。

然后k%len[i] 得出每一个循环要做的处理次数,然后对于每一个循环都处理一下,最后输出结果就行了。

 

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 205;
int v[maxn],n,k,a[maxn],len[maxn],num;
bool sign[maxn];
char c[maxn];
int main()
{
    int i,j,l,t;
 char tmp;
 //freopen("1.txt","w",stdout);
    while(scanf("%d",&n),n)
    {
    num = 0;
    len[0] = 0;
       for(i = 1;i <= n;i ++) 
    {
    scanf("%d",&v[i]);
    sign[i] = false;
    len[i] = 0;
    }
    for(i = 1;i <= n;i ++)   //求出每一个循环 的长度 和出发点。
    {
     if(!sign[i])
     {
      t = i;
      do
      {
       sign[t] = true;
       len[num] ++;
    t = v[t];
      }while(t != i);
      a[num++] = i;
     }
    }
       while(scanf("%d",&k),k)
       {
           getchar();
           gets(c+1);
           l = strlen(c+1);
     for(i = l+1;i <= n;i ++) c[i] = ' ';
     c[i] = '\0';
     for(i = 0;i < num;i ++) //对每一个循环都进行k次变换,当然根据周期为len[num]取余
     {
        l = k % len[i]; //进行l次循环。 复杂度最多 O(n^2) = 40000,可以接受。 
     for(j = 0;j < l;j ++)
     {
      t = a[i];
      tmp = c[t];
      do
      {
       swap(tmp,c[v[t]]);
       t = v[t];
      }while(t != a[i]);
     }
     }
     puts(c+1);
       }
       puts("");
    }
    return 0;
}


 

poj 1721

论文后面的原题来的。将置换群用Jordan置换表示。

 #include <iostream>
using namespace std;
const int maxn = 1005;
int v[maxn],v2[maxn],n,s;
int main()
{
    int i,j,t;
    while(scanf("%d%d",&n,&s)!=EOF)
    {
       for(i = 1;i <= n;i ++) scanf("%d",&v[i]);
       j = i = 1;
       v2[j] = 1;
       while(v[i] != 1)
       {
           v2[++j] = v[i];
           i = v[i];
       }
       t = 1;
       while(s > 0)
       {
          t = (t<<1)%n;
          s --;
       }
       v[1] = j = v2[1];
       for(i = 2;i <= n;i ++)
       {
          j += t;
          if(j > n) j -= n;
          v[j] = v2[i];
       }
       for(i = 1;i < n;i ++) v2[v[i]] = v[i+1];
       v2[v[n]] = v[1];
       for(i = 1;i <= n;i ++) printf("%d\n",v2[i]);
    }
    return 0;
}


 

poj 3128

重要的句子:Write a program that takes a permutation of the English alphabet as input and decides if it may be the result of performing some permutation twice.
判断输入的句子能不能是某一个字串的平方。分两种情况:

当循环中有偶数个数的时候,平方后变成两个相等的循环。当循环奇数时,循环没有裂开。论文中gcd(i,n)可得。所以判断循环中相同数目的偶数循环是不是偶数,是的话就可以变成某个串的平方了。

#include <iostream>
using namespace std;
const int maxn = 30;
bool sign[maxn];
int n,a[maxn],num[maxn];
char c[maxn];
int main()
{
    int i,j,b;
    scanf("%d",&n);
    while(n--)
    {
        scanf("%s",c);
        for(i = 0;i < 26;i ++)
           a[i] = c[i] - 'A',sign[i] = false,num[i] = 0;
        num[26] = 0;
        for(i = 0;i < 26;i ++)
        {
           if(!sign[i])
           {
               j = i;
               b  = 0;
               do{
                  sign[j] = true;
                  b ++;
                  j = a[j];
               }while(j != i);
               num[b] ++;
           }
        }
        for(i = 2;i <= 26;i +=2)  
            if(num[i]%2) break;
        if(i <= 26) puts("No");
        else puts("Yes");
    }
    return 0;
}


 

poj 3590

解题报告在:http://hi.baidu.com/zzningxp/blog/item/240fbc8ba9ee167a9e2fb428.html,参照着写就可以了。

#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 200;
int n;
bool sign[maxn];
int a[maxn],al,b[maxn],num,best[maxn],bl,ans,mul;
void init()
{
     int i,j;
     b[num++] = 2;
     for(i = 2;i < maxn;i += 2) sign[i] = true;
     for(i = 3;i < maxn;i ++)
     {
        if(!sign[i])
        {
            b[num++] = i;
            for(j = i*i;j < maxn;j += i)
               sign[j] = true;
        }
     }
}
void dfs(int sum,int i)
{
     int j;
  if(sum < b[i])
  {
   if(mul > ans)
   {
    ans = mul;
    for(j = 0;j < al;j ++) best[j] = a[j];
    bl = al;
    while(sum > 0)
    {
     best[bl++] = 1;
     sum --;
    }
   }
  }else
  {
   dfs(sum,i+1);
   for(j = b[i];j <= sum;j *= b[i])
   {
    a[al++] = j;
       mul *= j;
    dfs(sum - j,i+1);
    al --;
       mul /= j;
   }
  }
}
int main()
{
    int t,i,j,l;
    init();
    scanf("%d",&t);
    while(t--)
    {
        al = bl = 0;
        ans = 0;mul = 1;
        scanf("%d",&n);
        if(n == 1) puts("1 1");
        else
        {
        dfs(n,0);
        printf("%d",ans);
        sort(best,best+bl);
        for(i = 0,j = 1;i < bl;i ++)
        {
           for(l = j+1;l < j+best[i];l ++) printf(" %d",l);
           printf(" %d",j);
           j += best[i];
        }
        printf("\n");
        }
    }
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值