bzoj 1815: [Shoi2006]color 有色图 (置换+dfs)

19 篇文章 0 订阅

1815: [Shoi2006]color 有色图

Time Limit: 4 Sec   Memory Limit: 64 MB
Submit: 212   Solved: 102
[ Submit][ Status][ Discuss]

Description

Input

输入三个整数N,M,P 1< = N <= 53 1< = M < = 1000 N< P < = 10^ 9

Output

即总数模P后的余数

Sample Input

input 1
3 2 97

Sample Output

output 1
4

HINT

Source

[ Submit][ Status][ Discuss]

题解:置换+dfs

无向完全图如果只考虑点的话,那么就是可以等价成一个点。

因为这道题的置换是点的全排列,那么我们先考虑如何求点的置换。

我们的目标就是求出每个m(f)有多少个取值,然后快速计算,m(f)跟边置换有关,暂且不说,但是数量与点置换相关,我们现在考虑每个m(f)对应的点置换的数量。m(f)是否相同应该与点的划分方式有关(就是拆成几个轮换,以及每个轮换中元素的数量)。计算的时候利用组合数和阶乘。首先如果两个组的元素个数相同,那么在选取的时候先选哪一个都一样,所以需要除去出现次数的阶乘(就是这几个组的全排列)。然后对于组内的元素有(个数-1)的阶乘种排列方式,为啥是个数-1?因为我们要求每个位置都是乱的就是不能再拆成别的轮换。

现在考虑如何计算m(f).

分成两部分考虑:

(1)两个轮换之间的边,形成的轮换的个数是gcd(x,y),其中x,y为轮换中点的数量。

(2)同一轮换中的边,轮换的个数是size/2,我也不是很清楚为什么,只能画画图感受一下(其实就是找规律。。。)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 103
#define LL long long 
using namespace std;
int n,q[N],use[N],vis[N],num[N],len,m,k[N];
LL cnt,c[N][N],ans,p,d[N],jc[N],inv[N],a[N],g[N][N],b[N*N];
LL quickpow(LL num,int x)
{
    LL ans=1; LL base=num%p;
    while (x) {
        if (x&1) ans=ans*base%p;
        x>>=1;
        base=base*base%p;
    }
    return ans;
}
void init(int n)
{
    for (int i=0;i<=n;i++) c[i][0]=1;
    for (int i=1;i<=n;i++)
     for (int j=1;j<=i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%p;
    jc[0]=1;
    for (int i=1;i<=n;i++) jc[i]=jc[i-1]*i%p;
    for (int i=1;i<=n;i++) inv[i]=quickpow(jc[i],p-2);
    b[0]=1;
    for (int i=1;i<=n*n;i++) b[i]=b[i-1]*m%p;
}
int gcd(int x,int y){
    if (g[x][y]) return g[x][y];
    int r;
    while (y){
        r=x%y;
        x=y; y=r;
    }
    return g[x][y]=x;
}
void dfs(int x,int now)
{
    if (!now) {
        int n1=x-1; int sum=0;
        for (int i=1;i<=n1;i++)
         for (int j=i+1;j<=n1;j++) sum+=gcd(a[i],a[j]);
        for (int i=1;i<=n1;i++) sum+=a[i]/2;
        LL tot=1; int cnt=n;
        for (int i=1;i<=n1;i++)
         tot=tot*c[cnt][a[i]]%p,cnt-=a[i];
        for (int i=1;i<=n;i++)
         if (k[i]) tot=tot*inv[k[i]]%p;
        for (int i=1;i<=n1;i++) tot=tot*jc[a[i]-1]%p;
        ans=(ans+tot*b[sum])%p;
        //cout<<tot<<" "<<sum<<endl;
        return;
    }
    for (int i=a[x-1];i<=now;i++) {
        k[i]++; a[x]=i;
        dfs(x+1,now-i);
        k[i]--;
    }
}
int main()
{
    scanf("%d%d%lld",&n,&m,&p);
    if (n==1) {
        printf("1\n");
        return 0; 
    }
    a[0]=1;
    init(53); dfs(1,n);
    ans=ans*quickpow(jc[n],p-2)%p;
    printf("%lld\n",ans);
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值