[BZOJ2064]-分裂-状压dp思路好题
2017-12-16 10:52
357 查看
说在前面
一道训练思维的好题,然而me还没想多久就去看题解了真是浪费了一道题啊…后悔++
题目
BZOJ2064传送门题目大意
给出两个数列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中同时拿走,剩下的是一个子问题,而已经消耗的步数为i(初始)+j(当前合并)+1(拆分),而剩下的问题规模A:N−i2−j+1(因为拆分而增多)−1(因为拿走而减少),B:M−i1−1。递归终止时,A和B剩余规模都为1,此时i1=M−2,i2=N−j−1,而当前层操作所用步数为j+1,所以总步数为M+N−2
具体实现大概是这样的
定义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() ; }
相关文章推荐
- BZOJ 2064: 分裂 状压DP题解
- BZOJ 2064: 分裂 状压DP
- BZOJ 2064: 分裂 状压dp
- bzoj 2064: 分裂(状压dp)
- BZOJ 2064: 分裂 [DP 状压 转化]
- bzoj2064 分裂 状压dp(神题)
- [BZOJ2064]分裂(状压dp)
- BZOJ 2064: 分裂 状压dp
- [BZOJ 2064]分裂:状压DP
- bzoj2064 分裂(状压DP)
- BZOJ 2064: 分裂 状压dp
- [BZOJ2064]分裂(状压dp)
- bzoj 2064 分裂 状压dp
- [bzoj2064] 分裂 状压dp
- BZOJ_2064_分裂_状压DP
- BZOJ 2064: 分裂( 状压dp )
- BZOJ 2064: 分裂 | 状压DP
- 2064: 分裂 状压DP
- bzoj2064: 分裂(集合DP)
- BZOJ 2064: 分裂|状压动规