BZOJ2064 分裂

Address

Solution

  • 只可意会不可言传?不存在的。
  • 但感觉这题解法很神,代码也很神。
  • 可以知道操作次数的上界是 n + m 2 次( n 1 次合并成一块后再分裂 m 1 次)。
  • 考虑如果能把初始状态和目标状态分成和对应相等的两组,那么操作次数就变为 n + m 2 × 2 次。
  • 同样,如果能分为 k 组,操作次数就变为 n + m 2 k 次。
  • 于是问题就转化为求 k 的最大值。
  • 注意到 n 1 , n 2 的范围很小,把初始状态和目标状态中的所有块压缩成一个 20 位的二进制串,来表示它们选取的状态。
  • 把目标状态中块的面积取反,则问题就被转化为选取尽量多面积和为 0 的子集。
  • 记状态 i 的面积和为 s u m [ i ] ,则转移为 s u m [ i ] = s u m [ l o w b i t ( i ) ] + s u m [ i l o w b i t ( i ) ] (其实任选两个 i 的子集相加即可,这样写只是方便)。
  • 记状态 i 最多能选取的子集个数为 f [ i ] ,则转移为 f [ i ] = max j = 1 n 1 + n 2 { f [ i 2 j 1 ] } + ( s u m [ i ] == 0 ) (这里并不需要枚举子集转移,因为 i 的子集肯定被 i 2 j 1 的子集包含,之前已经转移过了)。
  • 时间复杂度 O ( 2 n 1 + n 2 × ( n 1 + n 2 ) )

Code

#include <iostream>
#include <cstdio>
#include <cctype>
#include <algorithm>
#include <cstring>
#include <cstdlib>

using namespace std;

inline int get()
{
    char ch; int res = 0; bool flag = false;
    while (ch = getchar(), !isdigit(ch) && ch != '-');
    (ch == '-' ? flag = true : res = ch ^ 48);
    while (ch = getchar(), isdigit(ch))
        res = res * 10 + ch - 48;
    return flag ? -res : res;
}

inline void CkMax(int &x, int y)
{
    if (x < y) x = y;
}

const int N = 11e5 + 5;
int n, m, sum[N], f[N];

int main()
{
    n = get();
    for (int i = 0; i < n; ++i) sum[1 << i] = get();
    m = get();
    for (int i = 0; i < m; ++i) sum[1 << n + i - 1] = -get();

    n += m;
    const int C = 1 << n;
    for (int i = 1; i < C; ++i)
    {
        int tmp = i & -i;
        sum[i] = sum[tmp] + sum[i ^ tmp];
        for (int j = 0; j < n; ++j)
            if (1 << j & i) CkMax(f[i], f[1 << j ^ i]);
        if (!sum[i]) ++f[i];
    }
    printf("%d\n", n - (f[C - 1] << 1));
} 

猜你喜欢

转载自blog.csdn.net/bzjr_log_x/article/details/81149240