【状态压缩DP】CF D. A Simple Task

【状态压缩DP】CF D. A Simple Task求环的个数

题目链接
image

铺垫

二进制的一些操作

寻找第一个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;
}
原文地址:https://www.cnblogs.com/BeautifulWater/p/15806846.html