1. 程式人生 > 其它 >【狀態壓縮DP】CF D. A Simple Task

【狀態壓縮DP】CF D. A Simple Task

【狀態壓縮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;
}