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的更多相关文章

  1. PAT 甲级 1154 Vertex Coloring

    https://pintia.cn/problem-sets/994805342720868352/problems/1071785301894295552 A proper vertex color ...

  2. pat甲级 1154 Vertex Coloring (25 分)

    A proper vertex coloring is a labeling of the graph's vertices with colors such that no two vertices ...

  3. PAT_A1154#Vertex Coloring

    Source: PAT A 1154 Vertex Coloring (25 分) Description: A proper vertex coloring is a labeling of the ...

  4. 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 ...

  5. PTA 1154 Vertex Coloring

    题目链接:1154 Vertex Coloring A proper vertex coloring is a labeling of the graph's vertices with colors ...

  6. PAT甲级——A1154 VertexColoring【25】

    A proper vertex coloring is a labeling of the graph's vertices with colors such that no two vertices ...

  7. django模型操作

    Django-Model操作数据库(增删改查.连表结构) 一.数据库操作 1.创建model表        

  8. 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 ...

  9. CF149D. Coloring Brackets[区间DP !]

    题意:给括号匹配涂色,红色蓝色或不涂,要求见原题,求方案数 区间DP 用栈先处理匹配 f[i][j][0/1/2][0/1/2]表示i到ji涂色和j涂色的方案数 l和r匹配的话,转移到(l+1,r-1 ...

  10. 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 ...

随机推荐

  1. Apache + PHP + Mysql Windows下配置

    1.安装Apache 下载网址 http://httpd.apache.org/download.cgi#apache24 二.下载php 下载地址:https://www.php.net/downl ...

  2. MQ(基本概念)

    MQ的基本概念 队列管理器:是MQ中最上层的一个概念,由它为我们提供消息队列服务. 消息:即应用程序发送给MQ托管的数据.其有两部分组成:消息描述符和消息体. 消息分为两种类型:永久型和非永久型. 永 ...

  3. 12-如何使用Genarator逆向工程

    使用逆向工程,帮我们更快的建立pojo类.mapper接口及xml映射文件等,无需手写,替代了一部分的mybatis功能. 一.导入MyGenarator逆向工程项目 二.修改xml配置文件 三.执行 ...

  4. laravel whereHas sum & addSelect sum

    $users = User::select('id', 'username', 'coins', 'cut') ->when(request()->has('agent_tip_sum') ...

  5. mysql zip安装步骤

    1. 官网下载社区版 https://dev.mysql.com/downloads/mysql/ 版本5.7或者8.0 2. 解压到指定的目录. 3.创建my.ini文件,编辑内容: [mysqld ...

  6. Codeforces Round #857 Div.1/Div.2 CF1801/1802 2A~2F 题解

    点我看题(Div2) Div 2A. Likes 如果要赞最多,肯定是先放所有的点赞,再放所有移除的操作.如果要最少,那就先把赞分成两种:最后被移除的和没被移除的:最后先放所有被移除的,放一个移除一个 ...

  7. Python学习笔记--PySpark的相关基础学习(一)

    PySpark包的下载 下载PySpark第三方包: 构建PySpark的执行环境入口对象 PySpark的编程模型 数据输入 对于SparkContext对象里面的成员方法parallelize,支 ...

  8. 自己动手从零写桌面操作系统GrapeOS系列教程——15.用汇编向屏幕输出字符

    学习操作系统原理最好的方法是自己写一个简单的操作系统. 在上一讲中我们介绍了屏幕显示的原理,本讲我们来实战一下. 一.向屏幕输出一个字符mbr4.asm mbr4.asm中的代码如下: ;将屏幕第一行 ...

  9. 如何针对海外不同地区进行音视频自动化测试?丨Dev for Dev 专栏

    近年来由于全球性的新冠疫情,世界各地对实时音视频的需求猛增.不同国家和地区由于经济发展.国家政策等原因,网络环境有很大不同,如果要做好音视频体验,就需要分地域进行音视频指标测试.但是不论是外包,还是云 ...

  10. day06-静态资源访问&Rest风格

    SpringBoot之静态资源访问&REST风格请求 1.SpringBoot静态资源访问 1.1基本介绍 只要静态资源是放在类路径下的:/static./public./resources. ...