说在前面
一道训练思维的好题,然而me还没想多久就去看题解了
真是浪费了一道题啊…后悔++
题目
题目大意
给出两个数列A,B,它们的长度分别为N,M(数字个数不超过10,数字均在[1,50]内)。A数列的和等于B数列的和。现在有两种操作:
将一个数字拆分成两个数字,这两个数字之和等于原来的那个数字,属于的数列不变(原来是A数列的,拆开之后还是A数列的);
将两个数字合并成一个数字,这两个数字之和就是新的那个数字,属于的数列不变(只能是同一数列的数字合并)
询问最少操作多少次可以将A变成B
输入输出格式
输入格式:
第一行描述A数列,其中第一个数N表示A中数字个数,接下来N个数表示数列A中的数字。
第二行描述B数列,输入B数列的方式与A相同
输出格式:
输出最少操作次数
解法
思路大概是这样的
首先可以确定的是,对于任何一组合法的A,B,A至多需要N+M-2次就可以变换到B,这是显然的。可以先经过N-1次操作把A数列合并成一个数,然后再经过M-1次操作拆分成B数列。
然而这样的步骤中可能有不必要的操作。如果说在把A合并成一坨的过程中,某个时刻这一坨已经和B中某些数字的和相等了,那么完全可以把这一坨直接拆分成B那些数字,而不需要继续和其他的A中的数字合并成更大的一坨再去拆分。
也就是说,如果A中有子集和B的某一个子集和相等,那么完全可以把这两个子集单独处理。每多一个 不相交的 且 和相等的 子集,总步数就会减少2,所以最后答案就是N+M-2*子集个数
而如果A中没有任何一个子集和B中任何一个子集和相等(全集和空集除外),那么无论是边合并边拆分,还是先合并后拆分,它们的步数都是一样的,为|A|+|B|-2。
证明:假设经过i次操作之后(其中
i1
次拆分,
i2
次合并),A中所有数字均已小于B(不然继续拆分)。现在将A中的数字不断合并,直到A中有一个数字大于B中某个数字,假设这时合并了
j
次。那么这个数字一定可以拆分成 B中的某个数字+另一个不在B中的数字(如果另一个数字也在B中,那么将A的这个数字加上已经拆分好的数字之和 等于 拆分之后的两个数字加上已经拆分好的数字之和,与假设矛盾)。那么把拆分出的 在B中的那个数字从A和B中同时拿走,剩下的是一个子问题,而已经消耗的步数为
具体实现大概是这样的
定义dp[stateA][stateB]表示在A选择的集合为stateA,在B选择的集合为stateB时,有多少不相交的子集相等。
如果当前stateA之和不等于stateB,那么
dp[stateA][stateB]=max(dp[stateA′][stateB],dp[stateA][stateB′])
如果当前stateA的和等于stateB,从中任意去掉一个元素之后,相等子集个数都会减一,反过来就有
dp[stateA][stateB]=max(dp[stateA′][stateB],dp[stateA][stateB′])+1
下面是自带大常数的代码
/**************************************************************
Problem: 2064
User: Izumihanako
Language: C++
Result: Accepted
Time:840 ms
Memory:2872 kb
****************************************************************/
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int N1 , N2 , a1[15] , a2[15] , Fstate1 , Fstate2 ;
short dp[1024][1024] , sum1[1024] , sum2[1024] ;
void preWork(){
Fstate1 = ( 1 << N1 ) - 1 ;
Fstate2 = ( 1 << N2 ) - 1 ;
short s , j ;
for( s = 0 ; s <= Fstate1 ; s ++ ){
for( j = 0 ; j < N1 ; j ++ )
if( s&(1<<j) ) sum1[s] += a1[j+1] ;
}
for( s = 0 ; s <= Fstate2 ; s ++ ){
for( j = 0 ; j < N2 ; j ++ )
if( s&(1<<j) ) sum2[s] += a2[j+1] ;
}
}
void solve(){
short s1 , s2 , k ;
for( s1 = 1 ; s1 <= Fstate1 ; s1 ++ ){
for( s2 = 1 ; s2 <= Fstate2 ; s2 ++ ){
short tmp = 0 ;
for( k = 0 ; k < N1 ; k ++ )
if( s1&(1<<k) ) tmp = max( dp[s1^(1<<k)][s2] , tmp ) ;
for( k = 0 ; k < N2 ; k ++ )
if( s2&(1<<k) ) tmp = max( dp[s1][s2^(1<<k)] , tmp ) ;
dp[s1][s2] = tmp + ( sum1[s1] == sum2[s2] ) ;
}
}
printf( "%d" , N1 + N2 - 2*dp[Fstate1][Fstate2] ) ;
}
int main(){
scanf( "%d" , &N1 ) ;
for( int i = 1 ; i <= N1 ; i ++ )
scanf( "%d" , &a1[i] ) ;
scanf( "%d" , &N2 ) ;
for( int i = 1 ; i <= N2 ; i ++ )
scanf( "%d" , &a2[i] ) ;
preWork() ;
solve() ;
}