P17691 彩虹鱼
一、题目大意
给定两个数组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求最长上升子序列
状态定义:dp[i][j] = pa 前 i 个元素 和 pb 前 j 个元素 的 LCS 长度
转移方程:如果pa[i]=pb[j]:这两个相等,可以配对
则dp[i][j]是前i-1和前j-1个元素中的最长公共子序列长度:dp[i][j]=dp[i-1][j-1];
如果pa[i]≠pb[j]:dp[i][j]=max(dp[i-1][j],dp[i][j-1])
五、代码
#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;
}
完结撒花!!!
浙公网安备 33010602011771号