B - A Funny Bipartite Graph
题意:
一个二分图,左右各有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 iif(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 ifor(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;}
}