BZOJ 2064 - 状压DP

传送门

题目大意:

给两个数组, 数组中的两个元素可以合并成两元素之和,每个元素都可以分裂成相应的大小,问从数组1变化到数组2至少需要多少步?

题目分析:

看到数据范围\(n<=10\), 显然是在提醒我们状压。用sum[i]表示i状态的面积总和。
对于任意一个数组\(1\)和数组\(2\)\(sum\)值必须相等才能变化),变化的最劣情况是将数组\(1\)合并成1个再分成数组\(2\),步数为\(n1 + n2 - 2\),然而如果数组\(1\)的一个子集\(k\)和数组\(2\)的一个子集\(l\)\(sum\)相等,\(k\)\(l\)又可以以同样的方法进行变化,使数组\(1\)到数组\(2\)的变化次数\(-2 = n1 + n2 - 2 * 2\),继续拓展,也就是说如果存在\(x\)个面积相等的子集,变化次数就为\(n1 + n2 - 2 * x\)
\(f[i][j]\)表示数组\(1\)\(i\)状态和数组\(2\)\(j\)状态存在的相同面积的子集数量,于是就可以枚举子集进行\(dp\)求出\(f[(1 << n1) - 1][(1 << n2) - 1]\),最后答案即为\(n1 + n2 - 2 * f[(1 << n1) - 1][(1 << n2) - 1]\)
看似复杂度会爆表,其实无用状态很多,因为只有\(sum\)相等才能进行变化。

code

580ms

#include<bits/stdc++.h>
using namespace std;

const int N = 15;
int n1, n2, a1[N], a2[N], sum1[1100], sum2[1100], f[1100][1100];

inline int count(int x){
    int ret = 0;
    while(x) ret++, x = x & (x - 1);
    return ret;
}

struct ioSys{
    inline int read(){
        int i = 0, f = 1; char ch = getchar();
        for(; (ch < '0' || ch > '9') && ch != '-'; ch = getchar());
        if(ch == '-') f = -1, ch = getchar();
        for(; ch >= '0' && ch <= '9'; ch = getchar())
            i = (i << 3) + (i << 1) + (ch - '0');
        return i * f;
    }

    inline void wr(int x){
        if(x < 0) putchar('-'), x = -x;
        if(x > 9) wr(x / 10);
        putchar(x % 10 + '0');
    }
    inline void operator >> (int &x){
        x = read();
    }
    inline void operator << (int x){
        wr(x);
    }
}IO;

int main(){
    freopen("h.in", "r", stdin);
    IO >> n1;
    for(int i = 1; i <= n1; i++) IO >> a1[i];
    for(int i = 0; i <= (1 << n1) - 1; i++){
        for(int j = 1; j <= n1; j++)
            if((1 << (j - 1)) & i) sum1[i] += a1[j];
        // cout<<sum1[i]<<" ";
    }
    // cout<<endl;
    IO >> n2;
    for(int i = 1; i <= n2; i++) IO >> a2[i];
    for(int i = 0; i <= (1 << n2) - 1; i++){
        for(int j = 1; j <= n2; j++)
            if((1 << (j - 1)) & i) sum2[i] += a2[j];
        // cout<<sum2[i]<<" ";
    }
    // cout<<endl;
    //---------------------------------------
    for(int i = 0; i <= (1 << n1) - 1; i++)
        for(int j = 0; j <= (1 << n2) - 1; j++){
            if(sum1[i] == sum2[j]){
                f[i][j]++;
                for(int k = i & (i - 1); k; k = i & (k - 1))
                    for(int l = j & (j - 1); l; l = j & (l - 1)){
                        // if(sum1[k] == sum2[l]) f[k][l]++;
                        f[i][j] = max(f[i][j], f[i - k][j - l] + f[k][l]);
                    }
            }
        }
    // cout<<f[(1 << n1) - 1][(1 << n2) - 1]<<endl;
    IO << n1 + n2 - 2 * f[(1 << n1) - 1][(1 << n2) - 1];
}

转载于:https://www.cnblogs.com/CzYoL/p/7657897.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值