ABC294Ex K-Coloring
Statement
对一张简单无向图进行 \(k\) 染色,满足对于每条边的两个端点颜色不同,求方案数。
\(n,m\leq 30\)。
Solution
无向图 \(k\) 染色问题,很经典的问题。
这道题的突破口是 \(n,m\) 均不大,所以 \(m-n\) 不会很大,这提示我们使用广义串并联图方法。
具体地,根据 EI 和洛谷讨论区里的说法,我们套路性地考虑对于每条边设 \(DP\),\(f_i\) 表示如果这条边两个端点被染了不同的颜色,这条边内部被缩略的结构中有多少种染色方案。\(g_i\) 表示如果这两条边端点被染了相同颜色的方案数。
那么对于原图中的边显然有 \(f_{u,v}=1\) 和 \(g_{u,v}=0\)。
广义串并联图方法的套路是对每条边设置 \(DP\) 后删一度点直接把贡献乘入答案,缩二度点和叠重边更新 \(DP\) 值。
下述推导来自讨论区:
对于删一度点,将答案乘上 \((k-1)f_u+g_u\),表示枚举删的这个点的颜色。
对于缩二度点,\(f_e=f_u f_v(k-2)+g_u f_v+f_u g_v,g_e=f_u f_v(k-1)+g_u g_v\),表示枚举中间那个点的颜色并分讨。
对于叠重边,\(f_e=f_u f_v,g_e=g_u g_v\),表示乘法原理。
现在图中的点满足了 \(n\leq \frac{2m}{3}\) 即 \(n\leq 20\)。
考虑对每一个颜色设一个集合幂级数,答案就是这些集合幂级数子集卷积的结果。
具体地,我们先假设所有边都取到了 \(f\) 的贡献,然后如果有一个颜色的集合包含了这条边的两个端点,就需要乘上一个 \(\frac{g}{f}\)。容易发现 \(f\) 总是非 \(0\) 的,所以一定存在逆元。这些贡献容易一遍 \(\text{FWT}\) 计算答案。
现在我们需要快速求集合幂级数 \(F\) 的 \(k\) 次方。然而 \(n\) 有 \(20\) 级别,所以需要 \(\ln\) 再 \(\exp\) 回去。复杂度是 \(O(2^n n^2)\) 的。
\(\ln,\exp\) 直接对占位幂级数 \(O(n^2)\) 求就可以了。
式子:
\(\ln:g_n=f_n-\frac{1}{n}\sum_{i=1}^{n-1} g_i i f_{n-i}\)。需要保证常数项为 \(1\)。
\(\exp:g_n=\frac{1}{n}\sum_{i=1}^{n} f_i i g_{n-i}\)。需要保证常数项为 \(0\)。
#include <cstdio>
using namespace std;
int read(){
char c=getchar();int x=0;
while(c<48||c>57) c=getchar();
do x=(x<<1)+(x<<3)+(c^48),c=getchar();
while(c>=48&&c<=57);
return x;
}
const int N=33,P=998244353;
typedef long long ll;
int qp(int a,int b=P-2){
int res=1;
while(b){
if(b&1) res=(ll)res*a%P;
a=(ll)a*a%P;b>>=1;
}
return res;
}
int n,m,k,res,cnt;
int f[N][N],g[N][N];
bool del[N];
int deg[N];
int F[1<<20];
int inv[21],id[N];
namespace Subset{
int n;
int f[21][1<<20];
int g[21][1<<20];
void inc(int &x,int v){if((x+=v)>=P) x-=P;}
void dec(int &x,int v){if((x-=v)<0) x+=P;}
void FWT(int *arr){
for(int i=1;i<(1<<n);i<<=1)
for(int j=0;j<(1<<n);j+=(i<<1))
for(int k=j;k<(j|i);++k) inc(arr[k|i],arr[k]);
}
void IFWT(int *arr){
for(int i=1;i<(1<<n);i<<=1)
for(int j=0;j<(1<<n);j+=(i<<1))
for(int k=j;k<(j|i);++k) dec(arr[k|i],arr[k]);
}
void getln(int *arr){
for(int i=0;i<=n;++i)
for(int s=0;s<(1<<n);++s) f[i][s]=g[i][s]=0;
for(int s=0;s<(1<<n);++s) f[__builtin_popcount(s)][s]=arr[s];
for(int i=0;i<=n;++i) FWT(f[i]);
for(int i=1;i<=n;++i){
for(int j=1;j<i;++j)
for(int s=0;s<(1<<n);++s)
dec(g[i][s],(ll)g[j][s]*j%P*f[i-j][s]%P);
for(int s=0;s<(1<<n);++s)
g[i][s]=((ll)g[i][s]*inv[i]+f[i][s])%P;
}
for(int i=0;i<=n;++i) IFWT(g[i]);
for(int s=0;s<(1<<n);++s) arr[s]=g[__builtin_popcount(s)][s];
}
void getexp(int *arr){
for(int i=0;i<=n;++i)
for(int s=0;s<(1<<n);++s) f[i][s]=g[i][s]=0;
for(int s=0;s<(1<<n);++s) f[__builtin_popcount(s)][s]=arr[s];
for(int i=0;i<=n;++i) FWT(f[i]);
for(int s=0;s<(1<<n);++s) g[0][s]=1;
for(int i=1;i<=n;++i){
for(int j=1;j<=i;++j)
for(int s=0;s<(1<<n);++s)
inc(g[i][s],(ll)f[j][s]*j%P*g[i-j][s]%P);
for(int s=0;s<(1<<n);++s)
g[i][s]=(ll)g[i][s]*inv[i]%P;
}
for(int i=0;i<=n;++i) IFWT(g[i]);
for(int s=0;s<(1<<n);++s) arr[s]=g[__builtin_popcount(s)][s];
}
}
int main(){
n=read();m=read();k=read();res=1;
for(int i=1;i<=m;++i){
int u=read(),v=read();
f[u][v]=f[v][u]=1;
++deg[u];++deg[v];
}
bool fl=1;
while(fl){
fl=0;
for(int u=1;u<=n;++u)
if(deg[u]==1){
fl=1;
del[u]=1;
for(int v=1;v<=n;++v)
if(f[u][v]){
--deg[u];--deg[v];
res=((ll)f[u][v]*(k-1)+g[u][v])%P*res%P;
f[u][v]=f[v][u]=0;
g[u][v]=g[v][u]=0;
}
break;
}
if(fl) continue;
for(int u=1;u<=n;++u)
if(deg[u]==2){
fl=1;
int x=0,y=0;
for(int v=1;v<=n;++v)
if(f[u][v]){if(x) y=v;else x=v;}
deg[u]=0;del[u]=1;
int ff=(ll)f[u][x]*f[u][y]%P;
int nf=((ll)ff*(k-2)+(ll)g[u][x]*f[u][y]+(ll)f[u][x]*g[u][y])%P;
int ng=((ll)ff*(k-1)+(ll)g[u][x]*g[u][y])%P;
f[u][x]=f[x][u]=f[u][y]=f[y][u]=0;
g[u][x]=g[x][u]=g[u][y]=g[y][u]=0;
if(!f[x][y]&&!f[y][x]){
f[x][y]=f[y][x]=nf;
g[x][y]=g[y][x]=ng;
}
else{
f[y][x]=f[x][y]=(ll)f[x][y]*nf%P;
g[y][x]=g[x][y]=(ll)g[x][y]*ng%P;
--deg[x];--deg[y];
}
break;
}
}
for(int i=1;i<=n;++i) if(!del[i]&&!deg[i]) res=(ll)res*k%P,del[i]=1;
for(int i=1;i<=n;++i)
if(!del[i]) id[i]=cnt++;
if(cnt){
inv[1]=1;Subset::n=cnt;
for(int i=2;i<=cnt;++i) inv[i]=(ll)inv[P%i]*(P-P/i)%P;
for(int i=0;i<(1<<cnt);++i) F[i]=1;
for(int i=1;i<=n;++i){
if(del[i]) continue;
for(int j=1;j<i;++j){
if(del[j]) continue;
if(f[i][j]){
res=(ll)res*f[i][j]%P;
int ver=(1<<id[i])|(1<<id[j]);
F[ver]=(ll)F[ver]*qp(f[i][j])%P*g[i][j]%P;
}
}
}
for(int i=1;i<(1<<cnt);i<<=1)
for(int j=0;j<(1<<cnt);j+=(i<<1))
for(int k=j;k<(j|i);++k) F[k|i]=(ll)F[k|i]*F[k]%P;
Subset::getln(F);
for(int i=0;i<(1<<cnt);++i) F[i]=(ll)F[i]*k%P;
Subset::getexp(F);
res=(ll)res*F[(1<<cnt)-1]%P;
}
printf("%d\n",res);
return 0;
}
ABC294Ex K-Coloring的更多相关文章
- PAT 甲级 1154 Vertex Coloring
https://pintia.cn/problem-sets/994805342720868352/problems/1071785301894295552 A proper vertex color ...
- pat甲级 1154 Vertex Coloring (25 分)
A proper vertex coloring is a labeling of the graph's vertices with colors such that no two vertices ...
- PAT_A1154#Vertex Coloring
Source: PAT A 1154 Vertex Coloring (25 分) Description: A proper vertex coloring is a labeling of the ...
- PAT Advanced 1154 Vertex Coloring (25 分)
A proper vertex coloring is a labeling of the graph's vertices with colors such that no two vertices ...
- PTA 1154 Vertex Coloring
题目链接:1154 Vertex Coloring A proper vertex coloring is a labeling of the graph's vertices with colors ...
- PAT甲级——A1154 VertexColoring【25】
A proper vertex coloring is a labeling of the graph's vertices with colors such that no two vertices ...
- django模型操作
Django-Model操作数据库(增删改查.连表结构) 一.数据库操作 1.创建model表
- Codeforces Round #369 (Div. 2)---C - Coloring Trees (很妙的DP题)
题目链接 http://codeforces.com/contest/711/problem/C Description ZS the Coder and Chris the Baboon has a ...
- CF149D. Coloring Brackets[区间DP !]
题意:给括号匹配涂色,红色蓝色或不涂,要求见原题,求方案数 区间DP 用栈先处理匹配 f[i][j][0/1/2][0/1/2]表示i到ji涂色和j涂色的方案数 l和r匹配的话,转移到(l+1,r-1 ...
- Codeforces Round #369 (Div. 2) C. Coloring Trees DP
C. Coloring Trees ZS the Coder and Chris the Baboon has arrived at Udayland! They walked in the pa ...
随机推荐
- lc.977 有序数组的平方
题目描述 给你一个按非递减顺序排序的整数数组nums,返回每个数字的平方组成的新数组,要求也按非递减顺序 排序. 输入:nums = [-4,-1,0,3,10] 输出:[0,1,9,16,100] ...
- 按正斜线输出M*N的矩阵
public static void outMatrix(int[][] array) { for(int row=0;row<array.length;row++) { int scolumn ...
- vue 缓存后台获取的token
代码 localStorage.setItem("token",res.data.data);// 用localStorage缓存token值
- python selenium 操作文件上传,并发操作时,文件选择窗口混乱解决方案
上传文件 使用的是 python + autoit 模块,这种方式有一个问题,当出现多条任务同时选择文件上传的时候,无法判断那个文件选择窗口的归属,从而出现上传了错误的文件! 解决方法: 要上载文件而 ...
- CSS 平滑滚动 scroll-behavior: smooth
凡是需要滚动的地方都加一句scroll-behavior:smooth 来提升滚动体验! 经常使用的锚点定位功能就有了平滑定位功能,如<a href="#">返回顶部& ...
- webgl 系列 —— 变换矩阵和动画
其他章节请看: webgl 系列 变换矩阵和动画 动画就是不停地将某个东西变换(transform).例如将三角形不停地旋转就是一个动画 和 CSS transform 类似,变换有三种形式:平移.缩 ...
- 深入理解 Python 虚拟机:整型(int)的实现原理及源码剖析
深入理解 Python 虚拟机:整型(int)的实现原理及源码剖析 在本篇文章当中主要给大家介绍在 cpython 内部是如何实现整型数据 int 的,主要是分析 int 类型的表示方式,分析 int ...
- 【CS231n assignment 2022】Assignment 2 - Part 2,优化器,批归一化以及层归一化
前言 博客主页:睡晚不猿序程 首发时间:2022.7.23 最近更新时间:2022.7.23 本文由 睡晚不猿序程 原创 作者是蒻蒟本蒟,如果文章里有任何错误或者表述不清,请 tt 我,万分感谢!or ...
- 制作微软原版Windows11 PE(含Powershell)
1.adksetup下载链接:https://download.microsoft.com/download/1/f/d/1fd2291e-c0e9-4ae0-beae-fbbe0fe41a5a/ad ...
- MySQL 主从复制的问题及解决方案
更多内容,前往 IT-BLOG 复制功能是构建 MySQL 的大规模.高性能的基础,也就是所谓的 "水平扩展" 架构.我们可以通过为服务器配置一个或多个备库.同时,复制也是高可用性 ...