题目
传送门 to BZOJ
BZOJ上,用管理员账号搜索“分裂”就有了(当然作者这个蒟蒻是没有办法的)。
题目描述
瓦罗兰大陆自古以来都处于混乱的局面,经常会出现不同的两股势力合并或者一股势力分裂成两股势力的事件,我们将这样的合并或分裂称作一次动乱。每股势力有对应的领土面积。动乱前后领土总面积不变,也就是说:两股面积为
S
1
S_1
S1 和
S
2
S_2
S2 的势力合并后面积为
S
1
+
S
2
S_1+S_2
S1+S2 ;面积为
S
S
S 的势力分裂成的两股面积分别为
S
1
,
S
2
S_1,S_2
S1,S2 的势力一定满足
S
1
+
S
2
=
S
S_1+S_2=S
S1+S2=S 。注意任意两股势力都可以合并,并不要求编号相邻。
现在已知上古时期的瓦罗兰有 n n n 股势力,每股势力的面积为 A i A_i Ai 。现在的瓦罗兰大陆有 m m m 股势力,每股势力面积为 B i B_i Bi 。求从上古时期到现在至少经历了多少次动乱。
输入格式
第一行一个正整数
T
(
1
≤
T
≤
5
)
T(1\le T\le 5)
T(1≤T≤5) 表示数据组数,对于每组数据:
第一行,首先一个数 n ( 1 ≤ n ≤ 10 ) n(1\le n\le 10) n(1≤n≤10) ,接下来是 n n n 个正整数表示 A 1 A_1 A1 到 A n ( 1 ≤ A i ≤ 50 ) A_n(1\le A_i\le 50) An(1≤Ai≤50) 。
第二行,首先一个数 m ( 1 ≤ m ≤ 10 ) m(1\le m\le 10) m(1≤m≤10) ,接下来是 m m m 个正整数表示 B 1 B_1 B1 到 B m ( 1 ≤ B i ≤ 50 ) B_m(1\le B_i\le 50) Bm(1≤Bi≤50) 。
保证 A A A 数组的和等于 B B B 数组的和。
输出格式
每组数据一个整数,表示至少经历了多少次动乱,保证答案在
i
n
t
int
int范围之内。
思路
说明: x ˉ \bar{x} xˉ 一律表示 U − { x } U-\{x\} U−{x} ; S ˉ \bar{S} Sˉ 一律表示 U − S U-S U−S (或称 C u S C_u S CuS
硫化铜)。
无论如何,将所有的都合在一起,再分裂成每一个,都是可以完成任务的,次数为 n + m − 2 n+m-2 n+m−2 。但这不是最优的——部分合并和分裂可以省略。
如果将 A A A 分成两个集合 S A 1 , S A 2 S_{A1},S_{A2} SA1,SA2 ,将 B B B 分成两个集合 S B 1 , S B 2 S_{B1},S_{B2} SB1,SB2 ,满足 ∑ x ∈ S A 1 x = ∑ x ∈ S B 1 x \sum _{x\in S_{A1}}x=\sum_{x\in S_{B1}} x ∑x∈SA1x=∑x∈SB1x ,那么只需要分别处理 S A 1 , S B 1 S_{A1},S_{B1} SA1,SB1 与 S A 2 , S B 2 S_{A2},S_{B2} SA2,SB2 即可,即省去了 S A 1 S_{A1} SA1 与 S A 2 S_{A2} SA2 的合并与分裂两次操作。
而这两个分别处理的子问题,与原问题的结构是一致的——如果这两个子问题又可以分成两个集合……所以运用动态规划,取得最大值。
考虑到 2 ≤ n + m ≤ 20 2\le n+m\le 20 2≤n+m≤20 ,用状态压缩求解(可以将两个数组直接连接在一起,将 B B B 数组的值置为相反数)。 f ( S ) f(S) f(S) 表示集合 S S S 中可以分出多少个相等集合,则
f ( S ) = max S 0 ⊂ S , s u m ( S 0 ) = 0 [ f ( S 0 ) ] + 1 f(S)=\max_{S_0\subset S,sum(S_0)=0}[f(S_0)]+1 f(S)=S0⊂S,sum(S0)=0max[f(S0)]+1
其中 s u m ( S ) = ∑ x ∈ S v x sum(S)=\sum_{x\in S}v_x sum(S)=∑x∈Svx 。不存在 S 0 S_0 S0 的情况下, f ( S ) = 1 f(S)=1 f(S)=1 。
这个式子的意义是假设 S 0 ˉ \bar{S_0} S0ˉ 无法再分,剩下的接着分。
但是这样的枚举子集就会变成 3 n + m , 3 20 = 3486784401 , O ( 3^{n+m},3^{20}=3486784401,\mathcal O( 3n+m,320=3486784401,O(肯定过不了 ) ) ) 。 😭
发现在这样的式子中,只有 s u m ( S ) = 0 sum(S)=0 sum(S)=0 时 f ( S ) f(S) f(S) 有意义。
这样以来,优化方向就很明显了,只需要让 2 n 2^n 2n 个 状态全部有意义即可。
令 g ( S ) = max S 0 ⊆ S f ( S 0 ) g(S)=\max_{S_0\subseteq S}f(S_0) g(S)=maxS0⊆Sf(S0) ,这个转移就可以写的很简单了:
g ( S ) = max x ∈ S g ( S ∩ x ˉ ) + [ s u m ( S ) = 0 ] g(S)=\max_{x\in S} g(S\cap \bar x)+[sum(S)=0] g(S)=x∈Smaxg(S∩xˉ)+[sum(S)=0]
其意义就是:
- 如果 s u m ( S ) = 0 sum(S)=0 sum(S)=0 ,那么当 x ∈ S 0 ˉ x\in \bar{S_0} x∈S0ˉ 时,即可取到最值。
- 如果 s u m ( S ) ≠ 0 sum(S)\ne 0 sum(S)=0 ,那么就应当去掉其中一个元素,以取到有意义的 f ( S ) f(S) f(S) 。
于是答案就是 n + m − 2 g ( U ) n+m-2g(U) n+m−2g(U) 。
代码
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int MaxN = 20;
int n, m, sum[1<<MaxN];
void init(){
scanf("%d",&n);
for(int i=0; i<n; ++i)
scanf("%d",&sum[1<<i]);
scanf("%d",&m);
for(int i=n; i<n+m; ++i){
scanf("%d",&sum[1<<i]);
sum[1<<i] = -sum[1<<i];
}
n += m;
for(int i=0; i<n; ++i)
for(int j=1; j<(1<<i); ++j)
sum[(1<<i)|j] = sum[1<<i]+sum[j];
}
int dp[1<<MaxN];
void solve(){
for(int i=0; i<(1<<n); ++i)
dp[i] = -1;
dp[0] = 0;
for(int S=1; S<(1<<n); ++S){
for(int i=0; i<n; ++i)
dp[S] = max(dp[S],dp[S^(1<<i)]);
// 因为初值全-1,若 S>>i&1==0,则S^(1<<i)>S,值为-1
dp[S] += (sum[S] ? 0 : 1);
}
printf("%d\n",n-(dp[(1<<n)-1]<<1));
}
int main(){
int T; scanf("%d",&T);
while(T --){
init();
solve();
}
return 0;
}