CodeForces - 11D A Simple Task

◇A Simple Task◇

+传送门+


先说两句

每次都是这样:上去讲一道题,一边讲一遍问:没问题吧(众人回应:笑)。讲罢,老师曰:“你等下写篇题解吧”……
于是,我又来了。


◇题意◇

给一个有 n n 个点,m条边的图,求图上环的个数。

(1n19,0m) ( 1   ≤   n   ≤   19 , 0   ≤   m )
注意:大家对着样例自己看一下
提示:有两个“八”字形的环


◇解析

状压 Dp D p

Dp D p 的理由:这个我没法解释,我主要是看出的状压 Dp D p
状压的理由: 1n19 1   ≤   n   ≤   19 就这样好吧。
开个玩笑,这道题很容易想到点集并且通过路径进行转移,所以就是状压 Dp D p

基本思路
状态定义

f[S][u] f [ S ] [ u ] :经过点集 S S 里的点的点到目标点u的路径数。

Example

这里写图片描述

下面很重要

特别注意:默认一条路径的起点为在二进制数编号在中最右端的节点,即节点编号最小的,称其为st,例如上图为1。
再次注意:为了下文表示方便,定义求一个点集 S S st的方法叫做 St(S) S t ( S )

状态转移

我们统计的是环的个数,什么时候会出现一个环呢,当然是目标点 u u 已经是这个点集S的起点 st s t
于是我们的得到答案的统计方法

ans+=f[S][u] a n s + = f [ S ] [ u ] (u=St[S]) ( u = S t [ S ] )

再考虑转移,显然对一个 f[S][u] f [ S ] [ u ] 的状态来说,它的来源有 f[S][v] f [ S ′ ] [ v ] ( S S ′ S S 除了u之外的点集, v v u的邻接点,也可看做此次转移的父亲节点)

Example

这里写图片描述

所以我们可以写出简单的方程了:

f[S][u]=f[S][v] f [ S ] [ u ] = ∑ f [ S ′ ] [ v ] (eg[u][v]) ( e g [ u ] [ v ] )

这里选择用 eg[][] e g [ ] [ ] 表示邻接矩阵,储存位置关系。

但很容易想到如果寻找 S S ′ 非常困难,所以我们在扫描到 S S ′ 就向 S S 递推,使实验和代码更优。
所以,改变一下转移的方程:
设当前状态f[S][v]

S=S|(1<<u) S ′ = S | ( 1 << u )
f[S][u]+=f[S][v] f [ S ′ ] [ u ] + = f [ S ] [ v ] (eg[u][v]) ( e g [ u ] [ v ] )

差不多了。


◇代码◇

下面的代码,我是存的 1n 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 再暴力

to t o be b e or o r not n o t to t o be b e , this t h i s is i s a a question.

还想说哦:这个题,挺经验的,我好像也该再学学语文。


Thanks T h a n k s for f o r reading r e a d i n g !

  • 13
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值