qoj856 Cactus
题意
给出一棵仙人掌图,问给每个点染 \(k\) 种颜色之一且相邻节点颜色不同的方案数为多少。
\(n\le 3\times 10^5,k\le 10^9\)。
思路
先考虑树怎么做。
很显然是 \(k\times (k-1)^{n-1}\),但是我太菜了第一步就没想出来。
因为树根节点可以染 \(k\) 种颜色,此后的每个节点都不能和父亲染一样的颜色,所以每个节点有 \(k-1\) 种方案。
由于仙人掌图可以缩成一棵树,所以再来考虑仙人掌上的环该怎么处理。
对于一条长为 \(n\) 的链,它的答案和树是一样的,环显然就是将链首尾拼接起来,但是会出现首尾颜色相同的情况,需要减掉。
当一条链首尾颜色相同时,第一个颜色一定和倒数第二个颜色不同,可以看作长为 \(n-1\) 的 环,因此长为 \(n\) 的环的方案数即为长为 \(n\) 的链的方案数减去长为 \(n-1\) 的环的方案数。
设长为 \(n\) 的链方案数为 \(f_n\),环为 \(g_n\),则有 \(f_n=k\times (k-1)^{n-1},g_n=f_n-g_{n-1}\)。
注意当 \(n=1,2\) 时需要特判(这里我们把一个点也看成长为 \(1\) 的环)。
跟树同样的,对于一个环,如果这个环不属于根节点,那么环上一个点的颜色不能与父亲相同,即方案数要乘 \(\frac{k-1}{k}\)。
于是跑一遍边双直接求出答案即可。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int md=1000000007;
int T,low[400005],dfn[400005],stk[400005],f[400005],g[400005],cnt,top,len,n,m,k,ans,inv;
vector<int> t[400005];
int fpow(int x,int y,int p){
int res=1,t=x;
while(y){
if(y&1) res=(res*t)%p;
t=(t*t)%p,y>>=1;
}
return res;
}
void tarjan(int x,int fa){
low[x]=dfn[x]=++cnt,stk[++top]=x;
for(int v:t[x]){
if(!dfn[v])
tarjan(v,x),low[x]=min(low[x],low[v]);
else if(v!=fa)
low[x]=min(low[x],dfn[v]);
}
if(low[x]>=dfn[x]){
int tot=0,i1=0;
do{
if(stk[top]==1) i1=1;
tot++,top--;
}
while(stk[top+1]!=x);
if(i1) ans=ans*g[tot]%md;
else ans=ans*g[tot]%md*inv%md;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
cin>>T;
while(T--){
cin>>n>>m>>k;
ans=1,len=0,cnt=0,top=0;
for(int i=1;i<=n;i++)
low[i]=dfn[i]=0,t[i].clear();
for(int x,y,i=1;i<=m;i++){
cin>>x>>y;
t[x].push_back(y);
t[y].push_back(x);
}
inv=(k-1)*fpow(k,md-2,md)%md;
for(int i=1;i<=n;i++){
f[i]=k*fpow(k-1,i-1,md)%md;
if(i<=2) g[i]=f[i];
else g[i]=((f[i]-g[i-1])%md+md)%md;
}
tarjan(1,0);
cout<<ans<<endl;
}
return 0;
}

浙公网安备 33010602011771号