B - A Funny Bipartite Graph

本文介绍了一种解决二分图问题的巧妙方法,通过利用题目中特定的边连接规则,采用状压动态规划技巧,将左侧和右侧节点的考虑范围优化,降低到 O(n*2^n) 的复杂度,适用于n<=18且节点属性和度数受限的情况。主要涉及概念包括二分图、动态规划、节点选择策略和优化技术应用。
摘要由CSDN通过智能技术生成

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 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;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值