◇A Simple Task◇
先说两句
每次都是这样:上去讲一道题,一边讲一遍问:没问题吧(众人回应:笑)。讲罢,老师曰:“你等下写篇题解吧”……
于是,我又来了。
◇题意◇
给一个有 n n 个点,条边的图,求图上环的个数。
(1 ≤ n ≤ 19,0 ≤ m) ( 1 ≤ n ≤ 19 , 0 ≤ m )
注意:大家对着样例自己看一下
提示:有两个“八”字形的环
◇解析
状压 Dp D p
Dp
D
p
的理由:这个我没法解释,我主要是看出的状压
Dp
D
p
;
状压的理由:
1 ≤ n ≤ 19
1
≤
n
≤
19
就这样好吧。
开个玩笑,这道题很容易想到点集并且通过路径进行转移,所以就是状压
Dp
D
p
。
基本思路
状态定义
f[S][u] f [ S ] [ u ] :经过点集 S S 里的点的点到目标点的路径数。
Example
下面很重要
特别注意:默认一条路径的起点为在二进制数编号在中最右端的节点,即节点编号最小的,称其为st,例如上图为1。
再次注意:为了下文表示方便,定义求一个点集 S S 的的方法叫做 St(S) S t ( S )
状态转移
我们统计的是环的个数,什么时候会出现一个环呢,当然是目标点
u
u
已经是这个点集的起点
st
s
t
。
于是我们的得到答案的统计方法
再考虑转移,显然对一个 f[S][u] f [ S ] [ u ] 的状态来说,它的来源有 f[S′][v] f [ S ′ ] [ v ] ( S′ S ′ 是 S S 除了之外的点集, v v 是的邻接点,也可看做此次转移的父亲节点)
Example
所以我们可以写出简单的方程了:
这里选择用 eg[][] e g [ ] [ ] 表示邻接矩阵,储存位置关系。
但很容易想到如果寻找
S′
S
′
非常困难,所以我们在扫描到
S′
S
′
就向
S
S
递推,使实验和代码更优。
所以,改变一下转移的方程:
设当前状态
f[S′][u]+=f[S][v] f [ S ′ ] [ u ] + = f [ S ] [ v ] (eg[u][v]) ( e g [ u ] [ v ] )
差不多了。
◇代码◇
下面的代码,我是存的
1−n
1
−
n
的编号,与二进制不太相符,大家可能看起来有点不舒服。
通过班上讲题同学的反应….我会在一些可能需要些提示的地方注解。
/*Wiz*/
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=int(2e1);
typedef long long LL;
LL n,m;
LL f[1<<MAXN][MAXN];
LL eg[MAXN][MAXN];
LL Left(LL s) {
LL ret=1;
while(s) {
if(s%2)
return ret;
s>>=1;ret++;
}
}//即上文中的St()
int main()
{
LL u,v,ans=0;
scanf("%lld%lld",&n,&m);
for(LL i=1;i<=m;i++) {
scanf("%lld%lld",&u,&v);
eg[u][v]=1,eg[v][u]=1;
}
LL S=(1<<n)-1;
for(LL i=1;i<=n;i++)
f[(1<<(i-1))][i]=1;//初始状态,每个点到自己
for(LL s=1;s<=S;s++)
for(LL u=1;u<=n;u++) {
if(f[s][u]==0)
continue;//这个状态是没作用的
LL fir=Left(s);
for(LL v=fir;v<=n;v++)
if(u!=v&&eg[u][v]) {
if((s&(1<<(v-1)))&&v==fir)
ans+=f[s][u];//看求解答案部分
else if(!(s&(1<<(v-1)))) {
LL ss=s|(1<<(v-1));
f[ss][v]+=f[s][u];//看转移部分
}
}
}
ans-=m;//这是减去互相连边的情况,举个例子:1->2->1(为什么不/2?看37行)
ans/=2;//每种都会算两次:“1->2->4->3->1=1->3->4->2->1”
printf("%lld",ans);
}
再说两句
为了继承以往博客中的诗性:
不能 Dp D p 再暴力
还想说哦:这个题,挺经验的,我好像也该再学学语文。
Thanks T h a n k s for f o r reading r e a d i n g !