SHUoj 421 零件组装(状压DP+子状态枚举)

34 篇文章 1 订阅

零件组装

发布时间: 2017年7月9日 18:17   最后更新: 2017年7月9日 21:04   时间限制: 1000ms   内存限制: 128M

现有 n 个零件,小Y花费了很多时间来收集它们,现在他想把零件拼在一起,拼完就可以召唤神龙了。已知零件之间存在相邻的关系,拥有相邻关系的零件在最终的组装结果中就是相邻的,并且组装过程中每次只能通过相邻关系来组合零件。小Y每次可以选择两个零件(也可以是两个零件块,或一个零件与一个零件块)拼起来,成为一个零件块,但要求拼接时必须在两个零件块(或零件)之间存在相邻的零件。除此之外这些零件两两之间有类似于磁力的排斥关系,当将两个零件或者零件块拼接在一起的时候,会受到两边的零件间的排斥力,排斥力的大小=两边零件的相互排斥对数*单侧零件个数的最大值(拼接完成的零件组合体中的零件之间排斥不计)。现在已知零件间的相邻关系和排斥关系,小Y自然想知道如何拼接不费力,因此需要求出将这些零件组装起来的最优方案,使得所有步骤的排斥力之和最小。

第一行有一个整数 T 表示数据组数。( T<=20
接着有 T 组数据,每组数据第一行是整数 n 表示零件个数。
接着依此有两个 nn 的矩阵,都只由 0 1 构成。( 2<=n<=14 )
其中第一个矩阵表示零件两两之间的相邻关系,第 i 行第 j 列为 1 表示第 i 个零件与第 j 个零件相邻,
第二个矩阵表示零件两两之间的排斥关系,第 i 行第 j 列为 1 表示第 i 个零件与第 j 个零件排斥。
数据保证矩阵根据对角线对称,并保证通过零件的相邻关系可以最终拼接完成。

每组输入一个整数表示拼接过程的最小排斥力之和。

1
4
0 0 1 1
0 0 1 0
1 1 0 0
1 0 0 0
0 1 0 1
1 0 1 1
0 1 0 0
1 1 0 0
6

解题思路:

    首先看到这个合并,我们就可以想到是一个状压dp。那么最原始的做法就是每次枚举两个状态合并,转移,这样复杂度是O(2^(2n)*n^2),显然会tle。

    可以发现,这样枚举状态,很多时候得到的两个状态是有重叠部分的,如果能够在枚举的时候直接就不出现他们,就可以快一些。于是我们就可以倒过来枚举子状态(枚举方法见代码)。这样每个元素就有三种状态:没选,在子状态1,在子状态2。时间复杂度就压缩到了O(3^n*n^2)还是会tle。

    现在最大的冗余度就在状态转移上了,在枚举的过程中,每个子状态都出现了多次,每次我们都计算他们的信息造成了很多时间的浪费,于是我们考虑使用预处理来降低转移的复杂度。我们来看一下转移方程:dp[s]=min(dp[s], dp[sub1]+dp[sbu2]+max(size(sub1), size(sub2)*fight_num(sub1, sub2))。对于每个状态的size我们可以很容易的预处理出来。问题就在两个子集的互斥数,每次我们都需要O(n^2)来计算。这时我们就可以用到容斥来优化,sub1和sub2之间的互斥数就等于s内部的互斥数-sub1内部的互斥数-sub2内部的互斥数。

    现在我们就可以对每个状态O(n^2)的预处理得到状态转移需要的信息。整个程序的时间复杂度就变成了O(max(2^n*n*2, 3^n)),这道题n最大取14,那么复杂度就是O(3^n)。


AC代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <vector>
#include <queue>
#include <stack>
#include <deque>
#include <string>
#include <map>
using namespace std;
#define INF 0x3f3f3f3f
#define LL long long
#define fi first
#define se second
#define mem(a,b) memset((a),(b),sizeof(a))

const int MAXN=14;
const int MAXS=(1<<MAXN);
int N,S;//零件数,状态数
int join[MAXN][MAXN],fight[MAXN][MAXN];//能否连接,是否互斥
int dp[MAXS],size[MAXS],fight_num[MAXS];//得到此状态的最小花费,此状态零件数,此状态内部互斥数
int can_join[MAXS];//第i位为1表示此状态和第i个零件可以连接

void init()//初始化
{
    for(int i=0;i<N;++i)
        for(int j=0;j<N;++j)
        {
            join[i][j]=fight[i][j]=0;
        }
    for(int i=1;i<S;++i)
    {
        dp[i]=INF;
        fight_num[i]=can_join[i]=0;
    }
    dp[0]=0;
    for(int i=0;i<N;++i)
        dp[1<<i]=0;
}

void pre_work(int s)//对于每个状态n^2的预处理出以后需要的信息
{
    vector<int> save;
    for(int i=0;i<N;++i)
        if((s>>i)&1)
            save.push_back(i);
    size[s]=(int)save.size();//预处理每个状态的零件数
    for(int i=0;i<save.size();++i)
    {
        for(int j=i+1;j<save.size();++j)
            if(fight[save[i]][save[j]])
                ++fight_num[s];//预处理每个状态的内部互斥数
        for(int j=0;j<N;++j)
            if(join[save[i]][j])
                can_join[s]|=(1<<j);//预处理每个状态的相连情况
    }
}

void divide(int s)//枚举子状态
{
    int sub=s;
    do{
        int a=sub,b=s^sub;//当前枚举的状态和对应的另一个状态
        if(can_join[a]&b)//b中有可以和a相连的零件
            dp[s]=min(dp[s],dp[a]+dp[b]+max(size[a],size[b])*(fight_num[s]-fight_num[a]-fight_num[b]));//状态转移
        sub=(sub-1)&s;//进入下一个子状态
    }while(sub);
}

int main()
{
    int T_T;
    scanf("%d",&T_T);
    while(T_T--)
    {
        scanf("%d",&N);
        S=1<<N;
        init();
        for(int i=0;i<N;++i)
            for(int j=0;j<N;++j)
                scanf("%d",&join[i][j]);
        for(int i=0;i<N;++i)
            for(int j=0;j<N;++j)
                scanf("%d",&fight[i][j]);
        for(int s=0;s<S;++s)
        {
            pre_work(s);
            divide(s);
        }
        printf("%d\n",dp[S-1]);
    }
    
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值