Problem A
给出一个$n$个点$m$条边的仙人掌图(每条边最多处于一个简单环中)。
使用$c$种颜色对这张图中的顶点染色,使得每一条无向边连接的两个点颜色不同。
求染色的方案数,$mod \ 998244353$的值。
对于$100\%$的数据满足,$ 1 \leq n,m \leq 10^6$
Solution :
对于一棵树的答案非常简单就是$c \times (c-1) ^ {n-1}$
对于一个大环,我们不妨计算这个环上的方案数。
设$f(n)$表示含有$n$个点的环上使用$c$种颜色染色的方案数,
非常显然,$f(1) = c$ , $f(2) = c(c-1)$.
若$c \geq 3$ 那么考虑对于所有节点不能和前一个刚被染色的节点重复染色,方案数就是$c \times (c-1)^{n-1}$
然后考虑,最后一个点和第一个点的颜色不能重复,若重复,我们可以将其看做一个点,那么就变成了一个子问题。
最终,$f(n) = c \times (c-1)^{n-1} - f(n-1)$
对于仙人掌图我们考虑v-DCC缩点(即找点双联通分量)
对于一个点,可能在多个简单环里。
那么对于这个点,只要被一个环计算过,那么在计算剩余环的时候应当认为这个点的颜色既定。
所以,若多于一个点在$k$个简单环里,我们只需要将总方案数直接除以$c^{k-1}$即可,这是由于该点在其他$k-1$个环中被认为可以被染$c$种颜色,然而事实上,这个点的颜色在第一个环包含它的时候已经被计算。
所以,本题只需要进行一遍tarjan的v-DCC缩点即可。
复杂度$O(n)$
# include <bits/stdc++.h> # define int long long using namespace std; const int N=1e6+10,mo=998244353; int dfn[N],low[N],n,m,tot,head[N],cnt,blo[N],f[N],c; vector<int>dcc[N]; stack<int>s; struct rec{ int pre,to; }a[N<<2]; void adde(int u,int v) { a[++tot].pre=head[u]; a[tot].to=v; head[u]=tot; } int Pow(int x,int n) { int ans = 1; while (n) { if (n&1) ans=ans*x%mo; x=x*x%mo; n>>=1; } return ans%mo; } void tarjan(int u) { dfn[u] = low[u] = ++dfn[0]; s.push(u); for (int i=head[u];i;i=a[i].pre){ int v=a[i].to; if (!dfn[v]) { tarjan(v); low[u]=min(low[u],low[v]); if (low[v]>=dfn[u]) { int z; ++cnt; do { z = s.top(); s.pop(); dcc[cnt].push_back(z); blo[z]++; }while (v!=z); dcc[cnt].push_back(u); blo[u]++; } } else low[u] = min(low[u],dfn[v]); } } main() { scanf("%lld%lld%lld",&n,&m,&c); for (int i=1;i<=m;i++) { int u,v; scanf("%lld%lld",&u,&v); adde(u,v); adde(v,u); } f[1] = c; f[2] = c*(c-1) % mo; for (int i=3;i<=n;i++) f[i]=((c*Pow(c-1,i-1)%mo-f[i-1])%mo+mo)%mo; tarjan(1); int ans = 1,ret = 0; for (int i=1;i<=n;i++) ret+=blo[i]-1; for (int i=1;i<=cnt;i++) ans = ans*f[dcc[i].size()]%mo; int ni = Pow(c,mo-2); ans = ans * Pow(ni,ret) % mo; printf("%lld\n",ans); return 0; }