ZOJ Problem Set - 4043 Virtual Singers(2018acm 青岛赛区热身赛)

题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5795

背景描述:
已经记不起来上一次做题是什么时候的事了。。。大概是上辈子?
要不是某只机房的脊蛙在群里毒我说我秒A。。。我才不做这题md
差点忘记说了。。。以后我一定要给我的ACM队起名叫:小脊蛙,呱尼玛!

解题报告:
首先题目中的序列称为序列 a a a b b b,规定定义序列 c = a ⋃ b c = a\bigcup b c=ab
性质1:将 b i b_i bi升序排列,若称对应的 a i a_i ai b j b_j bj为一对匹配 ( b i , a f i ) (b_i,a_{f_i}) (bi,afi),则最优解一定也满足 a f i &lt; a f i + 1 a_{f_i} &lt; a_{f_{i + 1}} afi<afi+1
证明:通过讨论 b i , b i + 1 , a f i , a f i + 1 b_i,b_{i + 1},a_{f_i} ,a_{f_{i + 1}} bi,bi+1,afi,afi+1的6种大小相对关系可以得出

性质2:将序列 c c c 升序排序,将其中的序列 b b b所有元素,以及选出来的序列 a a a元素全部标记,那么标记一定是一段一段的,那么对于每一段内部,含序列 a a a和序列 b b b元素个数相等,即各个段完全独立。
证明:这每一段的边界,要不是整个序列 c c c的边界,要不边界紧邻的非标记元素必然属于序列 a a a,如果一个属于序列 b b b元素匹配的不在本段中,则可以通过将匹配替换为边界紧邻的元素,而减小答案,因此每一个元素的匹配必在它所属的段内,因此段内属于 a a a b b b序列元素个数相等

有了这两个性质,做起来就简单了,每个段内的 a a a b b b元素相等,定义 a a a为-1, b b b为+1,求一个前缀和,那么一个段就是前缀和相同的两个位置之间的,但注意到我们只需要找到前缀和相同的离当前最近的那个位置,且这一段的答案很好算,因为这一段内必然是总满足 b i &lt; a f i bi &lt; a_{f_i} bi<afi或者 b i &gt; a f i bi &gt; a_{f_i} bi>afi,再做一个前缀和就好了,再根据以上 d p dp dp即可。。。

代码:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#define N 100000
#define N1 (N + 2) 
#define inf 1e15
//#define ABS(x) ((x > 0) ? x : (-x))
using namespace std;
typedef long long LL;
struct node
{
	LL val;
	bool ID;
};

node c[N1 << 1]; 
int l,n,m,last[N1 << 1],S1[N1 << 1];
LL S2[N1 << 1],f[N1 << 1],a[N1],b[N1];
inline bool cmp(node x,node y)
{
	return (x.val < y.val);
};

inline LL ABS(LL t)
{
	return (t > 0) ? t : (-t);
}
void init()
{
	scanf("%d%d",&n,&m);
	l = n + m;
	for (int i = 1;i <= n; ++i)
		scanf("%lld",&a[i]);
	for (int i = 1;i <= m; ++i)
		scanf("%lld",&b[i]);
	for (int i = 1;i <= n; ++i)
		c[i].val = a[i],c[i].ID = 0;
	for (int i = 1;i <= m; ++i)
		c[i + n].val = b[i],c[i + n].ID = 1;	
	sort(c + 1,c + l + 1,cmp);
	//for (int i = 1;i <= l; ++i)
	//cout<<c[i].val<<"("<<c[i].ID<<") ";
	//puts("");
		
}

void DO_IT()
{
	S1[0] = m; S2[0] = 0; f[0] = 0;
	for (int i = 0;i <= l; ++i) last[i] = -1;
	last[m] = 0;
	for (int i = 1;i <= l; ++i)
	{
		if (c[i].ID)
		{
	    	S1[i] = S1[i - 1] - 1;
		  	S2[i] = S2[i - 1] - c[i].val;
		  	int o = last[S1[i]];
		  	if (o == -1||f[o] == inf) f[i] = inf;
		  	else  f[i] = f[o] + ABS(S2[i] - S2[o]);
		}
		else
		{
			S1[i] = S1[i - 1] + 1;
			S2[i] = S2[i - 1] + c[i].val;
			f[i] = f[i - 1];
			int o = last[S1[i]];
			if (f[o] == inf) f[i] = inf;
			else f[i] = min(f[i],f[o] + ABS(S2[i] - S2[o]));
		}
		last[S1[i]] = i;
	}
		
}
int main()
{
	int T;
	scanf("%d",&T);
	while (T--)
	{
		init();
		DO_IT();
		printf("%lld\n",f[l]);
	}
}

总结:这道题怎么做其实并不重要,我只是非常想知道,脊蛙什么时候能找到妹子啊。。。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值