【题解】图 from 模拟赛 2024.2.14
B. 图
一张无向图,\(n\) 个点,\(m\) 条边,选择一个非空点集和两端都在集合内的边,求选出来的是一个连通块的方案数,模 \(2\)。
\(1\le n\le 50,\space |u-v|\le 12\)
连通块很难维护,最小表示法会炸。
一个小 trick:在难以处理的条件中,把题目要求的东西转化成另一个东西,条件完全不一样,但是求出后可以容易推出原来的东西。
比如这里,选择一个点集,然后对每个点黑白染色,同一个连通块的点颜色相同,不难发现染色方案数就是 \(2^{\text{连通块个数}}\)。
我们要的是只有一个连通块的方案数,如果个数 \(>2\),我们把对方案数 \(\bmod 4\) 即可去掉。
设 \(f[i,j]\) 表示前 \(i\) 个点,点 \(i-11\sim i\) 的染色状态为 \(j\),其中 \(0\) 表示不选这个点,\(1\) 表示染白,\(2\) 表示染黑的方案数,用三进制表示。
转移就变得很容易,最后答案对 \(4\) 去摸,注意存在所有点不选的方案数 \(1\) 种。
点击查看代码
#include<bits/stdc++.h>
#define ls(p) a[p].lc
#define rs(p) a[p].rc
#define fi first
#define se second
#define mkp make_pair
#define ll int
#define pir pair<ll,ll>
#define pb push_back
using namespace std;
const ll maxn=52, inf=1e9, mod=1e9+7, M=531441;
ll n,m,u,v,msk[maxn],f[maxn][M],c1[M],c2[M],ans;
int main(){
scanf("%d%d",&n,&m);
for(ll i=1;i<=m;i++){
scanf("%d%d",&u,&v);
if(u>v) swap(u,v);
msk[v]|=(1<<v-u-1);
}
for(ll i=0;i<M;i++){
for(ll j=1,x=i;j<=12;j++){
if(x%3==1) c1[i]|=(1<<j-1);
else if(x%3==2) c2[i]|=(1<<j-1);
x/=3;
}
}
f[0][0]=1;
for(ll i=1;i<=n;i++){
for(ll j=0;j<M;j++){
if(i==1&&j==2) break;
if(!f[i-1][j]) continue;
ll w=j*3%M;
(f[i][w]+=f[i-1][j])%=4;
if(!(c2[j]&msk[i])) (f[i][w+1]+=f[i-1][j])%=4;
if(!(c1[j]&msk[i])) (f[i][w+2]+=f[i-1][j])%=4;
}
}
for(ll i=0;i<M;i++) (ans+=f[n][i])%=4;
printf("%lld",ans>>1);
return 0;
}