题意:
一个二分图,左右各有n个点,左边第i个点有一个属性mi,它在一个图中的价值为midi,其中di为它在图中的度数(特殊的,如果度数为0,则价值为0),求一个该二分图的子图使得右边的每个点度数都不为0且总价值最小,输出最小价值。如果无解输出−1
有若干个限制条件(i,j)表示子图中左边的点i和j不能同时存在
保证:
原二分图中左边的每个点度数在[1,3]之间。
左边的i点和右边的j点连线当且仅当i ≤ j
n<=18
mi<=100
题解:
参考题解:
文章1
文章2
这个题的思路非常妙
首先根据数据范围确定方法为状压dp
我们既要维护左侧的点,也有维护右侧的点,两侧都是n,我们都用二进制取枚举,那么复杂度就是n * 22n,这样肯定不行,要先办法优化
注意题目中有说左侧的i选右侧的j,当且仅当i<=j,也就是说当我们考虑左侧的第i个点时,左侧的后n-i个还没选,右侧的前i个点必须全选(不然往后再也选不了),也就是说左侧的后n-i位和右侧的前i位都没啥用,所有我们可以将左侧的前i位和右侧的后n-i位拼成一起,这样2n就可以存下,复杂度就是O(n*2n)
这波操作就相当于计组里面将32 位整数乘除法,它把乘数和结果同时存在了一个 64 位整数上
妙哉妙哉
思路很难,代码也很难。。代码之后更新
代码:
#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;
int sumstate=lstate|nstate;
dp[nxt][sumstate] = min(dp[nxt][sumstate], 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;
}
}