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求最长上升子序列

状态定义: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;
}

完结撒花!!!