【HDU3923】类似uva10294项链种类polya计数+逆元

Invoker

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 122768/62768 K (Java/Others)
Total Submission(s): 1106    Accepted Submission(s): 465


Problem Description
On of Vance's favourite hero is Invoker, Kael. As many people knows Kael can control the elements and combine them to invoke a powerful skill. Vance like Kael very much so he changes the map to make Kael more powerful. 

In his new map, Kael can control n kind of elements and he can put m elements equal-spacedly on a magic ring and combine them to invoke a new skill. But if a arrangement can change into another by rotate the magic ring or reverse the ring along the axis, they will invoke the same skill. Now give you n and m how many different skill can Kael invoke? As the number maybe too large, just output the answer mod 1000000007.
 

Input
The first line contains a single positive integer T( T <= 500 ), indicates the number of test cases.
For each test case: give you two positive integers n and m. ( 1 <= n, m <= 10000 )
 

Output
For each test case: output the case number as shown and then output the answer mod 1000000007 in a line. Look sample for more information.
 

Sample Input
2 3 4 1 2
 

Sample Output
Case #1: 21 Case #2: 1
Hint
For Case #1: we assume a,b,c are the 3 kinds of elements. Here are the 21 different arrangements to invoke the skills / aaaa / aaab / aaac / aabb / aabc / aacc / abab / / abac / abbb / abbc / abcb / abcc / acac / acbc / / accc / bbbb / bbbc / bbcc / bcbc / bccc / cccc /
 

Source


题解是转的:
解题思路:

Polya计数。题目可转化为用c种颜色给n个珠子的项链染色,问一共有多少种颜色方案。本题要对结果取模1000000007

 

1.旋转。

将环顺时针旋转i格后,循环节个数为gcd(n,i), 染色方案为 ∑c^gcd(n,i) 其中 i=1,2,3,4,....n

2.翻转。

这里也得考虑两种情况。

当n为奇数时,共有n个循环节个数为(n/2+1)的循环群,还有的资料上说是环的个数为(n/2+1) ,注意这是计算机上的表示,n/2整型相除计算机得到的是整数,其实应该写成(n+1)/2。,染色方案为 n*c^(n/2+1)

为什么n个循环节个数为(n/2+1)的循环群呢?我的理解是这样的,或许不太对。。。

拿正三角形为例,给它三个顶点染色, 对称轴是一个顶点与其对边终点连线所在的直线,这样的直线有3(n=3,即n个顶点) 条,共有3(n)个循环群。假设第一个顶点在对称轴上,那么第二个顶点经过对称轴翻转肯定和第三个顶点重合,那么 (2,3)是一个循环节,(1)自己是一个循环节,循环节个数为2,即(n+1/2)。

当n为偶数时,共有n个循环群,其中有n/2个的循环节个数为(n/2 +1), 有n/2个的循环节个数为(n/2)。

拿正方形为例,四个顶点从左上角顺时针编号1,2,3,4.

当以1,3顶点连线所在直线为对称轴时(对角的两个顶点),这样对称轴有2个(n/2),经过翻转,2,4 重合,1和1重合,3和3重合,那么循环节的个数为3(2,4) (1)(3), 即(n/2+1)。 染色方案为 (n/2)*c^(n/2+1)

当以两条相对平行的边的中点连线所在直线为对称轴时,比如以线段1,2的中点和3,4的中点连线的所在直线为对称轴,这样的对称轴有两个(n/2),经过翻转,1,2重合,3,4重合,循环节的个数为2,(1,2)(3,4),即(n/2)。,也就是谁和谁重合,谁就和谁在一个循环节里。染色方案为(n/2)*c^(n/2)

 

最后累加方案得到ans, 再除以置换群的个数2*n,即 ans/(2*n)%mod即为最后答案。但这里要特别注意,ans是在计算过程中不断取模得到的数,ans,2*n都在模剩余系中,不能直接参与除法计算,因为有公式a*b%mod=(a%mod*b%mod)%mod,除法对取余不满足结合律,a/b!=((a%mod)/(b%mod))%mod ,在计算 ans/(2*n)%mod时,可以转化为 ans*inv(2*n)%mod ,其中 inv(2*n)是2*n关于mod的逆元,保证乘以inv(2*n)和除以 2*n 对于最后的答案取余mod是一样。

所以现在的问题是怎样求一个数关于模P的逆元。

 

方法1:扩展欧几里得。 ax=1(mod P), gcd(a,p)=1, 其中x为a的逆元,就是我们所求,ax=PY+1, ax-Py=1, 所以用扩展欧几里得可以求出x。

方法2:费马小定理: 如果模P是素数的话,那么inv(a)=pow(a,p-2)%p; 等式右边用快速幂运算可以得出。


代码自己写的

#define DeBUG
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <string>
#include <set>
#include <sstream>
#include <map>
#include <list>
#include <bitset>
using namespace std ;
#define zero {0}
#define INF 0x3f3f3f3f
#define EPS 1e-6
#define TRUE true
#define FALSE false
typedef long long LL;
const double PI = acos(-1.0);
//#pragma comment(linker, "/STACK:102400000,102400000")
inline int sgn(double x)
{
    return fabs(x) < EPS ? 0 : (x < 0 ? -1 : 1);
}
#define N 10005
const long long mod = 1000000007;
long long powmod[N];
LL gcd(LL a, LL b)
{
    return b ? gcd(b, a % b) : a;
}
LL Ext_gcd(LL a, LL b, LL &x, LL &y)
{
    if (b == 0)
    {
        x = 1, y = 0;
        return a;
    }
    LL ret = Ext_gcd(b, a % b, y, x);
    y -= a / b * x;
    return ret;
}
LL Inv(LL a, int m)   ///求逆元a相对于m
{
    LL d, x, y, t = (LL)m;
    d = Ext_gcd(a, t, x, y);
    if (d == 1) return (x % t + t) % t;
    return -1;
}
void init(long long kind, long long n)
{
    powmod[0] = 1;
    for (int i = 1; i <= n; i++)
    {
        powmod[i] = (powmod[i - 1] * kind) % mod;
    }
}
long long polya(long long kind, long long n)
{
    long long ans = 0;
    for (long long i = 1; i <= n; i++)
    {
        ans = (ans + powmod[gcd(i, n)]) % mod;//旋转情况
    }
    if (n & 1)//翻转情况,注意偶数有两种对角线
    {
        ans += (n * powmod[(n) / 2 + 1]) % mod;
    }
    else
    {
        ans += (n / 2) * powmod[(n / 2 + 1)] % mod;
        ans += (n / 2) * powmod[n / 2] % mod;
    }
    ans = (ans * Inv(2 * n, mod) + mod) % mod;
    return ans;
}
int cnt = 1;
int main()
{
#ifdef DeBUGs
    freopen("C:\\Users\\Sky\\Desktop\\1.in", "r", stdin);
#endif
    int T;

    scanf("%d", &T);
    long long kind, n;
    while (T--)
    {
        printf("Case #%d: ", cnt++);
        scanf("%I64d%I64d", &kind, &n);
        init(kind, n);
        printf("%I64d\n", polya(kind, n));
    }

    return 0;
}



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