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;
}

完结撒花!!!

posted @ 2026-10-12 22:59  Haku_king  阅读(3)  评论(0)    收藏  举报