【状态压缩DP】CF D. A Simple Task求环的个数
铺垫
二进制的一些操作
寻找第一个1 lowbit
lowbit是树状数组中的老熟人了,原理是运用了原码和补码的特性。
int inline lowbit(int x)
{
return x&(-x)
}
合并状态 利用|运算
sta1|sta2
查找第k位上的数字是1,还是0
(s>>k)&1
思路
我们可以用一串01数字,来表示各个位置的占用情况。
比如101001,(1表示灯亮,0表示灯灭),故而在这里,我们可以知道1、3、6号灯处于亮的状态,2、4、5号灯处于灭的状态。
因而,我们可以定义一个二维dp数组d[i][j]
,数字i用来表示当前连线的点,j代表当前连线的终点,d[i][j]
表示拥有状态i表示的点的集合且以点j为结尾的连线的个数。
同时为了避免重复,我们对起点进行枚举。
举个例子,我们可以对加入公司的员工按ta们的起始工资进行分类,这样的分类方式,必然不会出现重复。
(在代码实现中,若新加入的点小于起点的编号,那么就不考虑该点的加入)
- 其他
- ABC(A)和ACB(A)这两个环本质上是一样的
- ABA不符合题目标准,且在dp过程中有且仅有会产生m个
- 因而,需要对答案进行修正,(ans-m)/2.
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 20 ;
ll d[1<<N][N],g[N][N],n;
ll maxn,m,q;
int inline lowbit(int x)
{
return x&(-x);
}
void init()
{
cin>>n;
maxn = (1<<n)-1;
cin>>m;
for(int i=1;i<=m;i++)
{
int a,b;
cin>>a>>b;
a--;b--;
g[a][b] = g[b][a] = 1;
}
for(int i=0;i<n;i++)
d[1<<i][i] = 1;
}
ll dp()
{
ll ans = 0;
for(int i=0;i<=maxn;i++)//状态从小到大进行枚举,也保证了正常的顺序
{
int t = lowbit(i);
for(int j=0;j<n;j++) //枚举状态i的结尾顶点
if(!d[i][j] || (1<<j)<t) continue;//不存在和小于起点,直接跳过
else for(int k=0;k<n;k++) //尝试去接上新的点
{
if(!g[j][k]||(1<<k)<t) continue;//如果不导通或者小于起点,直接跳过。
if( (i>>k)&1 )
{
if( t == (1<<k) )
ans += d[i][j];
}
else
d[ (1<<k)|i ][k] += d[i][j];
}
}
ans = (ans-m)/2;
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
init();
cout<<dp();
return 0;
}