【狀態壓縮DP】CF D. A Simple Task
阿新 • • 發佈:2022-01-15
【狀態壓縮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]
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; }