POJ2888 Magic Bracelet [矩阵快速幂+Burnside+欧拉函数]

M a g i c   B r a c e l e t Magic\ Bracelet Magic Bracelet


D e s c r i p t i o n \mathcal{Description} Description
给一条长度为 n n n的项链,有 m m m种颜色,另有 k k k条限制,每条限制为不允许 x , y x,y x,y颜色连在一起。
求有多少种本质不同的染色方式,本质不同的两种染色方式旋转不能互相得到 .


正 解 部 分 正解部分
对于 置换: “旋转 k k k 个单位”
在没有颜色限制的情况下, 染色方案数为 M g c d ( N , k ) M^{gcd(N,k)} Mgcd(N,k),

在有颜色限制的情况下,
不 同 循 环 节 的 元 素 相 互 之 间 是 挨 着 的 , \color{red}{不同循环节的元素相互之间是挨着的,} ,

  • 要保证第 i i i 个与第 i + 1 , i − 1 i+1,i-1 i+1,i1个循环节颜色不同 .
  • 由于是环, 还需保证第 1 1 1个与第 N N N个循环节颜色不同 .

在以上限制下, 如何求解方案数呢?
F [ i , j ] F[i,j] F[i,j] 表示前 i i i 个元素, 第 i i i 个元素颜色为 j j j 的方案数, l d [ i , j ] = 1 / 0 ld[i,j]=1/0 ld[i,j]=1/0 表示 i i i j j j 是否可以放到一起.
考虑到首尾相连, 枚举第 1 1 1个循环节选的 c o l col col 颜色, 进行 d p dp dp,

{ F [ 1 , c o l ] = 1 ,   F [ i , j ] = ∑ k = 1 M l d [ j , k ] ∗ F [ i − 1 , k ]           i ∈ [   2 , g c d ( N , k )   ) ,   F [ g c d ( N , k ) , j ] = ∑ k = 1 , k   ! =   c o l M l d [ j , k ] ∗ F [ g c d ( N , k ) − 1 , k ] \begin{cases}F[1,col]=1,\\ \ \\ F[i,j]=\sum_{k=1}^{M}ld[j,k]*F[i-1,k]\ \ \ \ \ \ \ \ \ i∈[\ 2,gcd(N,k)\ ), \\ \ \\ F[gcd(N,k),j]=\sum_{k=1,k\ !=\ col}^{M}ld[j,k]*F[gcd(N,k)-1,k] \end{cases} F[1,col]=1, F[i,j]=k=1Mld[j,k]F[i1,k]         i[ 2,gcd(N,k) ), F[gcd(N,k),j]=k=1,k != colMld[j,k]F[gcd(N,k)1,k]

于是 ∑ i = 1 M F [ g c d ( N , k ) , i ] \sum_{i=1}^{M}F[gcd(N,k),i] i=1MF[gcd(N,k),i] 即为该置换方案数 , 暂存到 F [ g c d ( N , k ) , 0 ] F[gcd(N,k),0] F[gcd(N,k),0] 里.

中 间 的 式 子 可 以 使 用 矩 阵 快 速 幂 优 化 \color{blue}{中间的式子可以使用矩阵快速幂优化} 使


再看我们要求的式子
L = 1 N ∑ i = 1 N F [ g c d ( N , k ) , 0 ] L=\frac{1}{N}\sum_{i=1}^{N}F[gcd(N,k),0] L=N1i=1NF[gcd(N,k),0]
变为
L = 1 N ∑ d ∣ N F [ d , 0 ] ∗ φ ( N d ) L=\frac{1}{N}\sum_{d|N}F[d,0]*\varphi(\frac{N}{d}) L=N1dNF[d,0]φ(dN)

至于为什么请点击这里看拓展2

时间复杂度 O ( M 4 ∗ N ∗ l o g N ) O(M^4*\sqrt{N}*logN) O(M4N logN).

如果你耐心的看到了这里, 那么不幸的告诉你, 这个方法会 TLE

建立一个 M ∗ M M*M MM 的矩阵, C i , j = 1 C_{i,j}=1 Ci,j=1 表示 i i i 可以走到 j j j,
计算出这个矩阵 N g c d ( N , k ) \frac{N}{gcd(N,k)} gcd(N,k)N 次方, 则对角线的值加起来即为该置换的方案总数 .


注意 求解 p h i phi phi的时候不能 模.

调了一个晚上, 竟然是 p h i phi phi忘记加特判了!!


#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstring>
#define reg register

const int mod = 9973;

int read(){
        char c;
        int s = 0, flag = 1;
        while((c=getchar()) && !isdigit(c))
                if(c == '-'){ flag = -1, c = getchar(); break ; }
        while(isdigit(c)) s = s*10 + c-'0', c = getchar();
        return s * flag;
}

int N;
int M;
int K;

struct Matrix{ int C[12][12]; Matrix(){ memset(C, 0, sizeof C); } } A;

Matrix Modify(Matrix a, Matrix b){ // 
        Matrix s;
        for(reg int i = 1; i <= M; i ++)
                for(reg int j = 1; j <= M; j ++){ 
                        int &t = s.C[i][j];
                        for(reg int k = 1; k <= M; k ++)
                                t = (t+a.C[i][k]*b.C[k][j]) % mod;
                }
        return s;
}

Matrix KSM(Matrix a, int b){ //
        Matrix s;
        for(reg int i = 1; i <= M; i ++) s.C[i][i] = 1;
        while(b){
                if(b & 1) s = Modify(s, a);
                a = Modify(a, a);
                b >>= 1;
        }
        return s;
}

int phi(int x){ //
        int s = x;
        for(reg int i = 2; i*i <= x; i ++){
                if(x % i) continue ;
                s = s/i*(i-1);
                while(x%i == 0) x /= i;
        }
        if(x > 1) s = s/x*(x-1);
        return s%mod;
}

int KSM_2(int a, int b){ //
        int s = 1;
        a %= mod;
        while(b){
                if(b & 1) s = (s*a) % mod;
                a = (a*a) % mod;
                b >>= 1;
        }
        return s;
}

int gcd(int a, int b){ return !b?a:gcd(b, a%b); } //

int Calc(int k){
        int tmp = 0;
        Matrix B = KSM(A, k);
        for(reg int i = 1; i <= M; i ++) tmp += B.C[i][i], tmp %= mod; 
        tmp = (phi(N/k)*tmp) % mod;
        return tmp;
}

void Work(){
        N = read(), M = read(), K = read();
        for(reg int i = 1; i <= M; i ++)
                for(reg int j = 1; j <= M; j ++) A.C[i][j] = 1;
        for(reg int i = 1; i <= K; i ++){
                int a = read(), b = read();
                A.C[a][b] = A.C[b][a] = 0;
        }
        int Ans = 0;
        for(reg int k = 1; k*k <= N; k ++)
                if(N % k == 0){
                        Ans = (Ans + Calc(k)) % mod; 
                        int k2 = N/k;
                        if(k != k2) Ans = (Ans + Calc(k2)) % mod;
                }
        Ans = 1ll*Ans * KSM_2(N, mod-2) % mod;
        printf("%d\n", Ans);
}

int main(){
        int T = read();
        while(T --) Work();
        return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值