Virtual Singers-zoj4043-据说是青岛热身赛题目?

把a,b混在一起排序,常规操作~~a标记-1,b标记1.

嗯,首先我们可以考虑两个方向,一个是仿照最大流求,最值,一个是dp,因为像这种求数值的题目,方向就是这样,还有其他方向,欢迎留言。

然后注意两个性质:

1:如果一个区间内a,b数目都相同,那么这个区间内的a是不会和这个区间之外的b进行匹配的。

2:言下之意就是我们要确定从那个位置开始确定区间,因为同一个位置的a可能在多个能够满足上述区间里,也就是说,我们必须要在这些区间里选一个。怎样选,就靠dp拉。

3.还有一个要注意的要点是如果我们找到了一个区间,要去求这个区间里的最值,你当然不能暴力求解了,注意到性质1,如果我们把这个区间的两边的两个数去掉后就是个子区间,这个子区间的值通过我们前面求的值是能求出的,用一些最值减去就好了,为什么能减掉?因为要满足最值,你可以用反证法试试,而且你一定注意性质1.,所以我们可以看出在我们要求的区间里,这些子区间是一整块,一整块的,它们并没有和区间外的任何数值相连.(这是线性dp较难分析的部分,不过dp的关键不就是用以前求的来更新现在么。)

具体做法就是先把a,b混在一起排序,然后把a标记为1,b标记为-1,如果某段和为0那么这段里的a,b一定要相互匹配才能有最大值。

因为段与段之间会重合,这就涉及到某个段是选还是不选的问题所以我们要dp,目的就是选出其中的段不相互重合的最大值。

求出dp[i]表示匹配了i个的最值。

another[i]表示在位置i之前尽量匹配完的最值。也就是说如果3个a,6个b那么就只能配3个,而不是4个或者2个

tempdp[i]表示区间位置一定是从i开始的最值,它和another[i]的不同就是another[i]求出的最值,开始的那个区间不一定是从i开始的

pre[i],前面有相同前缀和的数出现的位置。主要是为了选出和为0的段而设计的。

注意pre【】这个数组一定要开大点啊!!我被坑惨了!!400000左右吧,否则错得很离谱~~

 

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
struct numm
{
	int k;
	ll v;
};
bool com(numm a, numm b)
{
	return a.v < b.v;
}
numm num[250000];
int n, m, all, t;
ll dp[120000];
ll tempdp[250000],another[250000];
int posnum1,posnum2,pre[500000];
int main()
{
	scanf("%d", &t);
	while (t--)
	{
		scanf("%d%d", &n, &m);
		all = n + m; posnum1 = 0; posnum2 = 0;
		for (int i = 1; i <= n; i++)
		{
			scanf("%d", &num[i].v);
			num[i].k = 1;
		}
		for (int i = n + 1; i <= all; i++)
		{
			scanf("%d", &num[i].v);
			num[i].k = -1;
		}
		sort(num + 1, num + all + 1, com);
		int sum = 0;
		for (int i = 0; i <=2 * all; i++)
			tempdp[i] = 0, pre[i] = -1,another[i]=0;
		pre[all] = 0; 
		for (int i = 0; i <= m; i++)
			dp[i] = 2000000000000000;
		dp[0] = 0;
		for (int i = 1; i <= all; i++)
		{
			sum += num[i].k;
			int temppsum = sum + all;
			if (num[i].k < 0)
				posnum1++;
			else
				posnum2++;
			if (pre[temppsum] == -1)
			{
				tempdp[i] = dp[min(posnum1,posnum2)];
			}
			else
			{
				int realpre = pre[temppsum] + 1;
				if ((i - realpre) == 1)
				{
					tempdp[i] = another[realpre - 1] + abs(num[i].v - num[realpre].v);
				}
				else
				{
					tempdp[i] = tempdp[i - 1] - another[realpre] + abs(num[i].v - num[realpre].v) + another[realpre - 1];
				}
			}
			int r = min(posnum1, posnum2);
			dp[r] = min(dp[r], tempdp[i]);
			another[i] = dp[r];
			pre[temppsum] = i;
		}
		printf("%lld\n", dp[m]);
	}
}

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值