一、题目大意
给定两个数组a(长度n)和b(长度m),每次可以:
合并:把相邻两个数变成一个数(它们的和),数组长度-1
拆分:把一个数拆分成两个数(和不变),数组长度+1
求把a变成b的最小操作次数,若不可能则输出-1
二、关键观察
观察1 :总和不变
数组总和不变,则易知当sum(a)≠sum(b)时,不可能把数组a变成数组b,输出-1
观察2 :可转化为分段问题
我们把a数组分成g段,b数组也应分成g段,要使对应段的和相等:
假设下面是a和b数组:
a: 2, 4 , 1, 5
b: 3, 3 , 2, 4
操作过程:
第一步 :合并a的每段
a数组为2, 4, 1, 5
合并 2,4 得 6
合并 1,5 得 6
现a数组为6,6,共2段
原来的4段变成2段,操作了2次,即n-g次
第二部 :把a拆分成b
a数组为6,6
b数组为3,3,2,4
拆分第一个6为3+3,拆分第二个6为2+4
a数组变成3,3,2,4,和b数组一样,操作了2次,即m-g次
此时可知共操作了4次即m+n-2g*次
要想使得操作次数m+n-2g最小,就要最大化段数g
三、最大化段数
我们需要找到一个切分点,a和b数组从1到这个切分点所有数的的和相等,那么我们不难想到用前缀和
对于上述样例:
a: 2, 4 , 1, 5
其前缀和数组pa为2,6,7,12
b: 3, 3 , 2, 4
其前缀和数组pb为3,6,8,12
不难发现两个前缀和数组中都有6和12,也就是说a和b数组中共有2个子序列中元素的和是相同的,那么问题就转化成了找到前缀和数组pa和pb中相同元素的个数,即pa和pb数组的LCS(最长上升子序列)
四、dp求最长上升子序列
状态定义:dpij = pa 前 i 个元素 和 pb 前 j 个元素 的 LCS 长度
转移方程:如果pai=pbj:这两个相等,可以配对
则dpij是前i-1和前j-1个元素中的最长公共子序列长度:dpij=dpi-1j-1;
如果pai≠pbj:dpij=max(dpi-1j,dpij-1)
五、代码
cpp
#include <bits/stdc++.h>
using namespace std;
#define fr(i,a,b) for(int i=a;i<=b;i++)
#define int long long
int a[5005],b[5005];
int pa[5005];
int pb[5005];
int dp[5005][5005];
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int T;
cin>>T;
while(T--)
{
memset(a,0,sizeof a);
memset(b,0,sizeof b);
memset(pa,0,sizeof pa);
memset(pb,0,sizeof pb);
int n,m;
cin>>n>>m;
fr(i,1,n)
{
cin>>a[i];
pa[i]=pa[i-1]+a[i];
}
fr(i,1,m)
{
cin>>b[i];
pb[i]=pb[i-1]+b[i];
}
if(pa[n]!=pb[m])
{
cout <<-1<<"\n";
continue;
}
fr(i,0,n)
{
fr(j,0,m)
dp[i][j]=0;
}
fr(i,1,n)
{
fr(j,1,m)
{
if(pa[i]==pb[j]) dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}//dp求LCS长度
cout <<n+m-2*dp[n][m]<<"\n";
}
return 0;
}
完结撒花!!!