计数题
题目链接
n n n个点的完全图,对边染色(颜色有 m m m种),求本质不同的染色方案数,答案对 p p p取模
1 ≤ n ≤ 53 , 1 ≤ m ≤ 1000 , 1 ≤ p ≤ 1 0 9 1\le n\le53,1\le m\le1000,1\le p\le10^9 1≤n≤53,1≤m≤1000,1≤p≤109
我们乍一看是染色问题,我们就想到了Polya定理
l = 1 ∣ G ∣ ∑ a i ∈ G k λ ( a i ) l=\frac{1}{|G|}\sum_{a_i\in G}k^{\lambda(a_i)} l=∣G∣1ai∈G∑kλ(ai)
我们可以用其式子来计算
但是我们发现染色的是边而不是点,而是边,所以我们计算的置换是对于边的
我们考虑先不枚举边置换,而是枚举点的置换,容易发现,一个点置换,其对应的边置换是唯一确定的
这样一来,就有了方便的枚举置换方式
我们发现,点的置换有 n ! n! n!种,我们发现 53 ! 53! 53!的复杂度显然不能接受
我们考虑不直接枚举所有点置换,而是枚举本质不同的点置换,本质相同的点置换,对应的边置换也是本质相同的
这样的话复杂度就是 n n n的整数划分了,随便写个程序就可以发现当 n = 53 n=53 n=53时,整数划分是 3 ∗ 1 0 5 3*10^5 3∗105级别的,目前看起来可以接受
code
#include
#include
#define rg register
template<typename T>inline void read(T&x){char cu=getchar();x=0;bool fla=0;while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}while(isdigit(cu))x=x*10+cu-'0',cu=getchar();if(fla)x=-x;}
template<typename T>inline void printe(const T x){if(x>=10)printe(x/10);putchar(x%10+'0');}
template<typename T>inline void print(const T x){if(x>=0)printe(x);else putchar('-'),printe(-x);}
int n,ans;
void dfs(const int t,const int h)
{
if(t==0)ans++;
else for(rg int i=h;i<=t;i++)dfs(t-i,i);
}
int main()
{
read(n);
dfs(n,1);
print(ans);
return 0;
}
我们考虑现在得到了一个整数划分,其有 k k k个循环,按循环长度排序后的第 i i i个循环长度为 L i L_i Li即 L 1 ≤ L 2 ≤ ⋅ ⋅ ⋅ ≤ L k L_1\le L_2\le···\le L_k L1≤L2≤⋅⋅⋅≤Lk
我们思考对于这样一个点置换,其对应的边置换的循环数量(在Polya中要求的东西)
两个端点在同一个长度为 L L L的点循环内的情况:
我们发现,对于任意一条边,将其两边的点转动,这样就可以产生一个大小为 L L L的边循环
然后这个时候有个特例,当 L L L为偶数时,有一个大小为 L 2 \frac{L}{2} 2L的循环,即环上每组相对的点所组成的循环
即:
L L L为奇数时,循环数为 ( L 2 ) L = L ∗ ( L − 1 ) / 2 L = L − 1 2 \frac{\binom{L}{2}}{L}=\frac{L*(L-1)/2}{L}=\frac{L-1}2 L(2L)=LL∗(L−1)/2=2L−1
L L L为偶数时,循环数为 ( L 2 ) + L 2 L = L ∗ ( L − 1 ) / 2 + L / 2 L = L 2 \frac{\binom{L}{2}+\frac{L}{2}}{L}=\frac{L*(L-1)/2+L/2}{L}=\frac L2 L(2L)+2L=LL∗(L−1)/2+L/2=2L
综上,这里的边循环数为 ⌊ L 2 ⌋ \left\lfloor\frac L2\right\rfloor ⌊2L⌋
两个端点分别在长度为 L a L_a La、 L b L_b Lb的点循环内的情况:
容易发现,这里的边循环长度为 l c m ( L a , L b ) lcm(L_a,L_b) lcm(La,Lb)
那么循环数量为 L a ∗ L b l c m ( L a , L b ) = g c d ( L a , L b ) \frac{L_a*L_b}{lcm(L_a,L_b)}=gcd(L_a,L_b) lcm(La,Lb)La∗Lb=gcd(La,Lb)
这样的话,对于一个枚举到的本质不同的点循环,其对Poyla定理中后面那个sigma的指数 C C C就为
C = ∑ i = 1 k ⌊ L i 2 ⌋ + ∑ i = 1 k ∑ j = i + 1 k ( L i , L j ) C=\sum_{i=1}^k\left\lfloor\frac {L_i}2\right\rfloor+\sum_{i=1}^k\sum_{j=i+1}^k(L_i,L_j) C=i=1∑k⌊2Li⌋+i=1∑kj=i+1∑k(Li,Lj)
我们也知道颜色数,现在还需要统计这种点循环的出现次数
容易发现,出现次数 S S S可以直接计算
我们设 T i T_i Ti为长度为 i i i的 L L L出现的次数(另外,定义 0 ! = 1 0!=1 0!=1)
S = ∏ i = 1 k ( L i − 1 ) ! ( n − ∑ j = 1 i − 1 L j L i ) ∏ i = 1 n T i ! = n ! ∏ i = 1 n T i ! ∗ ∏ i = 1 k L i S=\frac{\prod_{i=1}^k(L_i-1)!\binom{n-\sum_{j=1}^{i-1}L_j}{L_i}}{\prod_{i=1}^{n}T_i!}=\frac{n!}{\prod_{i=1}^{n}T_i!*\prod_{i=1}^{k}L_i} S=∏i=1nTi!∏i=1k(Li−1)!(Lin−∑j=1i−1Lj)=∏i=1nTi!∗∏i=1kLin!
这样的话我们就能快速的求出 S S S
最终答案就可以算出来了
l = S ∗ m C n ! l=\frac{S*m^C}{n!} l=n!S∗mC
这份代码为了方便理解,写的复杂度比较大,实际可以有更好的写法使程序跑得更快
#include
#include
#define rg register
typedef long long ll;
template<typename T>inline void read(T&x){char cu=getchar();x=0;bool fla=0;while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}while(isdigit(cu))x=x*10+cu-'0',cu=getchar();if(fla)x=-x;}
template<typename T>inline void printe(const T x){if(x>=10)printe(x/10);putchar(x%10+'0');}
template<typename T>inline void print(const T x){if(x>=0)printe(x);else putchar('-'),printe(-x);}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
ll n,m,p,L[61],fac[61],ans,tim[61];
inline ll pow(ll x,ll y)
{
ll res=1;
for(;y;y>>=1,x=x*x%p)if(y&1)res=res*x%p;
return res;
}
void dfs(const int step,const int t,const int h)
{
if(t==0)
{
ll xs=0,xf=1;
for(rg int i=1;i<step;i++)
{
xs+=L[i]>>1;
for(rg int j=i+1;j<step;j++)xs+=gcd(L[i],L[j]);
xf=xf*L[i]%p;
}
for(rg int i=1;i<=n;i++)xf=xf*fac[tim[i]]%p;
ans+=pow(m,xs)*pow(xf,p-2)%p;
}
else for(rg int i=h;i<=t;i++)
{
L[step]=i;
tim[i]++;
dfs(step+1,t-i,i);
tim[i]--;
}
}
int main()
{
read(n),read(m),read(p);
fac[0]=1;for(rg int i=1;i<=60;i++)fac[i]=fac[i-1]*i%p;
dfs(1,n,1);
print(ans%p);
return 0;
}
代码非常的清真,良好的计数技巧配上Poyla定理就能解决这题
计数好难