To_Heart—题解——操作
简单题意
有n堆石子,每堆石子都有一定的数量,第i堆石子的数量用Ai表示。任意两堆石子均可合并,一堆石子也可以分成两堆非空的石子。 一次合并或一次分裂都算一次操作。经过若干次操作以后,石子还剩m堆,告诉你m堆石子的数量Bi,问至少经历了几次操作。
题解
首先看一下数据范围,发现n,m均小于10,相加也不会超过20,所以考虑状压。
然后我们可以发现一个性质:对于两个元素,先将它进行合并之后一定不会比它分解更劣。举个例子:
现有 A 1 , A 2 A_1,A_2 A1,A2两个元素,想用它来并出 B 1 , B 2 , B 3 B_1,B_2,B_3 B1,B2,B3那么考虑两种情况:
- 先将 A 1 , A 2 A_1,A_2 A1,A2合并成 s u m sum sum,再将 s u m sum sum分成 B 1 , B 2 , B 3 B_1,B_2,B_3 B1,B2,B3
- 先将 A 1 , A 2 A_1,A_2 A1,A2分成 s u m sum sum,再将 B 1 , B 2 , B 3 B_1,B_2,B_3 B1,B2,B3合并成 s u m sum sum
此时1、2两种方法的消费是相同的。
然后可以归纳证明上面所提的性质。
所以我们只需要把所有操作都看成合并就好了。
然后我们还可以发现一个性质,对于任何两个数组,其操作次数不会大于(n+m-2),即把两堆均合并在一起。
如果我们可以找到初始集合的某个子集的元素和与目标集合的某个子集的元素和相等,那么我们可以少合一次,少分一次,也就是说我们每多找到一个元素和相等的子集我们的次数就可以-2。
所以现在问题就转换成了怎么分最多子集能相对应。
我们考虑把两个数组放在一起,并把其中的一个变为其相反数。
然后此时考虑状压就行了。
设dp[s]为当此时状态为s时,分出的最多子集且这些子集和均为0的个数。但不是每个状态都可以恰好分成若干个子集和为0的子集,所以 d p [ s ] = max ( d p [ s ] , d p [ s ⊕ ( 1 < < i ) ] ) , 1 ≤ i ≤ ( n + m ) & ( s & ( 1 < < i ) ) dp[s]=\max(dp[s],dp[s\oplus(1<<i)]),1\leq i \leq (n+m) \And (s\&(1<<i)) dp[s]=max(dp[s],dp[s⊕(1<<i)]),1≤i≤(n+m)&(s&(1<<i))
然后当状态为s时,所有值相加为0,就说明可以此状态刚好可以分成若干个子集和为0的子集,所以dp[s]++。
那么就完了。
代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[105];
int val[5000005];
int dp[5000005];
int Lowbit(int x){
return x&(-x);
}
int main(){
int T;
cin>>T;
while(T--){
memset(dp,0,sizeof dp);
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
val[(1<<i)]=a[i];
}
scanf("%d",&m);
for(int i=n;i<n+m;i++){
scanf("%d",&a[i]);
a[i]=-a[i];
val[(1<<i)]=a[i];
}
int len=1<<(n+m);
dp[0]=0;
for(int i=1;i<len;i++){
int now=i;
int sum=0;
while(now){
int nownow=Lowbit(now);
now^=nownow;
dp[i]=max(dp[i],dp[i^nownow]);
sum+=val[nownow];
}
if(!sum&&i^(len-1))
dp[i]++;
}
printf("%d\n",(n+m)-2-dp[len-1]*2);
}
return 0;
}