放个链接:AT2143(翻译在讨论中有,也可以直接到AtCoder去看英文题面)
首先对于一条边,分三种情况讨论:
1、不在任何环上,贡献为
(废话)。
2、在多个互相有交集的点双连通分量中,设
为点双连通分量的大小,(实践得出)通过旋转可以使这个点双连通分量中的任意两条边互换,故贡献为
3、在一个单一点双连通分量上(在一个简单环上):由Pólya定理可知贡献为
其中
为置换群的模,
为置换
的循环节个数(这里的置换即一次旋转操作)。
那为啥
?我们的
在这里还有一层含义:将置换按旋转格数排序后,
为顺时针旋转的格数。所以在变换
下,原本位置为
的边的位置会变为
。因此对于每条原本在
位置的边都需要
次旋转回到原位。而使得
相等的
的个数也是
,故循环节的长度为
。
代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<set>
#include<stack>
using namespace std;
const long long p=1000000007ll;
long long ans=1ll,fact[201],invfact[201];
int N,M,K,x,y,idx,low[51],dfn[51];
stack<int> s;
set<int> tmp;
vector<int> point[51];
long long qpow(long long a,int x){
long long s=1ll;
while(x){if(x&1)(s*=a)%=p;(a*=a)%=p;x>>=1;}
return s;
}
inline long long C(int n,int m){return fact[n]*invfact[m]%p*invfact[n-m]%p;}
void dfs(int u,bool root=false){
low[u]=dfn[u]=++idx;s.push(u);
for(int v:point[u])if(!dfn[v]){
dfs(v);low[u]=min(low[u],low[v]);
if(low[v]==dfn[u]){
tmp.clear();int cnt=1,siz=0,lst;
do tmp.insert(lst=s.top()),s.pop(),++cnt;while(lst!=v);tmp.insert(u);
for(int x:tmp)for(int y:point[x])if(tmp.count(y))++siz;
if((siz>>=1)<cnt)(ans*=K)%=p;else if(siz==cnt){long long sum=0ll;for(int i=0;i<cnt;++i)(sum+=qpow(K,__gcd(i,cnt)))%=p;(ans*=sum*qpow(cnt,p-2)%p)%=p;}else (ans*=C(siz+K-1,K-1))%=p;
}
}else low[u]=min(low[u],dfn[v]);
if(root)s.pop();
}
int main(){
scanf("%d%d%d",&N,&M,&K);
fact[0]=invfact[0]=1ll;for(int i=1;i<=M+K;++i)fact[i]=fact[i-1]*i%p;invfact[M+K]=qpow(fact[M+K],p-2);for(int i=M+K-1;i;--i)invfact[i]=invfact[i+1]*(i+1)%p;
for(int i=1;i<=M;++i)scanf("%d%d",&x,&y),point[x].push_back(y),point[y].push_back(x);
for(int i=1;i<=N;++i)if(!dfn[i])dfs(i,true);
printf("%lld",ans);
}