題意
一個二分圖,左右各有個點,左邊第個點有一個屬性,它在一個圖中的價值爲,其中爲它在圖中的度數(特殊的,如果度數爲,則價值爲),求一個該二分圖的子圖使得右邊的每個點度數都不爲且總價值最小,輸出最小价值。如果無解輸出
有若干個限制條件表示子圖中左邊的點和不能同時存在
保證:
- 原二分圖中左邊的每個點度數在之間。
- 左邊的點和右邊的點之間右邊當且僅當
數據範圍
解題思路
這個數據範圍很容易想到狀壓DP,但是有一個困難就是它既需要前面選擇的左邊的點的狀態,又需要當前沒有選擇的右邊的點的狀態,如果都用二進制去枚舉,那麼複雜度是。
注意到題目保證了:左邊的點和右邊的點之間右邊當且僅當
那麼從小到大爲左邊的點選擇連邊,當前選擇點的時候,右邊編號爲的點肯定都被覆蓋了,而左邊編號爲[i+1,n]的點肯定都還沒選擇。那麼這兩個部分的二進制長度加起來剛好是n。
所以我們從小到大爲左邊的點選擇連邊進行狀態轉移,爲選擇連邊時,的位代表前面選擇的左邊的點,位表示已經選擇了的右邊的點。然後枚舉點連邊情況進行轉移即可。
代碼:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int inf = 0x3f3f3f3f;
int dp[2][1<<18];
int val[20], ban[20];
vector<int> g[20];
char s[20];
int n;
void init(){
scanf("%d", &n);
for(int i = 0; i < n; ++i) g[i].clear();
for(int i = 0; i < n; ++i) {
scanf("%s", s);
for(int j = 0; j < n; ++j) if(s[j] == '1') g[i].push_back(j);
}
for(int i = 0; i < n; ++i) {
scanf("%s", s); ban[i] = 0;
for(int j = 0; j < i; ++j) if(s[j] == '1') ban[i] |= (1<<j);
}
for(int i = 0; i < n; ++i) scanf("%d", &val[i]);
}
int sol(){
int cur = 0, nxt = 1;
memset(dp, 0x3f, sizeof dp);
dp[cur][0] = 0;
for(int i = 0; i < n; ++i){
for(int mask = 0; mask < (1<<n); ++mask){
int lstate = mask&((1<<i)-1);
int rstate = mask&((1<<n)-(1<<i));
if(dp[cur][mask] == inf) continue;
// don't choose i
if(rstate>>i&1) dp[nxt][(mask)^(1<<i)] = min(dp[nxt][(mask)^(1<<i)], dp[cur][mask]);
if(ban[i]&lstate) continue;//can't choose i
for(int t = 1; t < (1<<g[i].size()); ++t){
int cost = 1;
int ex = 0;
for(int j = 0; j < g[i].size(); ++j){
int v = g[i][j];
if(t>>j&1) cost *= val[i], ex |= 1<<v;
}
int nstate = rstate|ex;
if( !(nstate>>i&1) ) continue;
dp[nxt][lstate|nstate] = min(dp[nxt][lstate|nstate], dp[cur][mask] + cost);
}
}
swap(cur, nxt);
memset(dp[nxt], 0x3f, sizeof dp[nxt]);
}
int ans = inf;
for(int i = 0; i < (1<<n); ++i) ans = min(ans, dp[cur][i]);
if(ans == inf) return -1;
return ans;
}
int main()
{
int T;cin>>T;
while(T--){
init();
cout<<sol()<<endl;
}
}