Description
小闪最近迷上了二刀流——不过他耍的其实是剑——新买了一个宝库用来专门存放自己收集的双剑。一对剑有两把,分只能左手用的和只能右手用的,各自有一个攻击力数值。虽然一对剑在小闪刚拿到时是一对,不过其实可以认为它们是独立的两把剑。一对剑的攻击力是左右两把剑的攻击力之和,小闪可以自由地搭配左右剑来练习二刀流。每次小闪得到一对新的双剑,他都可以去更新一次自己的双剑宝库,重新给每把剑配对。小闪需要自己给每一把剑配对,然后他的智能宝库会提供宝库中攻击力最高的一对剑来给小闪练习;不过小闪其实是一个很低调的人,不喜欢锋芒毕露,希望自己练习时的双剑攻击力尽可能小——虽然不知道怎么改变宝库,但是他可以改变双剑的搭配。每次新得到一对剑,更新完宝库之后,小闪练习时用的剑可能就会发生变化。请你求出每次更新宝库后小闪练习二刀流时的双剑攻击力。
Input
输入包含不超过10组数据。对于每组数据,第一行一个整数N(1<=N<=100000),表示小闪前后共收集到了N对双剑;接下来N行,每行包含两个整数A和B(1<=A,B<=100),分别表示每次小闪收集到的一对剑的左手用剑和右手用剑的攻击力数值。每组数据之后会有一个空行。输入以一行一个整数0结束。
Output
对于每组数据,输出N行,每行一个整数,表示每次更新后小闪练习二刀流时的双剑攻击力值。相邻两组数据之间输出一个空行。
Sample Input
3 2 8 3 1 1 4 3 1 1 2 2 3 3 0
Sample Output
10 10 9 2 3 4
觉得这题比较有意思,思想也是比较新颖,记录一下。
首先,作为一只菜鸡,我的第一想法是把左右剑数组分别快排,一个从小到大,另一个从大到小。分组配对求和,取最大值,这样一定能得到正确的结果。证明如下:
左剑值 a0 a1 a2 a3 a4 a5 a6..................................an-1 (有小到大) |
右剑值 b0 b1 b2 b3 b4 b5 b6..................................bn-1 (由大到小) |
匹配和 S0 S1 S2 S3 S4 S5 S6...............................Sn-1 |
假如Sm=am+bm (m<n)是最目前所有匹配和中最大值(最优解),如果存在另外的匹配方式使得最优解可以更小一些,例如Sk=am+bk<Sm,就推出 bk<bm 故 ak>am ,导致 bm+ak>Sm,也就是说Sk是伪最优解,不成立。
综上可知,这种方法理论可行(第一次做我也确实用过)。但是,由于这题的后台数据较多,每收集一把剑就要快排,复杂度高达O(N^2logN),会爆掉。于是就有了下面这种巧妙地方法。
首先注意到,每组数据的两个值A\B都在100内,这其实就很适合用一种看似简单的排序(简单到很容易忘了利用)——桶排序。能想到这里,其余的实现方法的原理就同上了。不过由于桶排序,不能简单的加一减一保持两个数组取值匹配,就对当前位置的值计数。总之,想办法实现上表所示的匹配就好。
代码如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=105;
int l[maxn],r[maxn];
int main()
{
int N;
int a,b;
while(~scanf("%d",&N))
{
fill(l,l+maxn,0);
fill(r,r+maxn,0);
if(N==0)break;
for(int i=0;i<N;i++)
{
//桶排序
scanf("%d%d",&a,&b);
l[a]++;
r[b]++;
int max1=-1;
int kl=0;
int kr=100;
int cntl=l[kl];
int cntr=r[kr];
while(kl<=100&&kr>=0)
{
if(cntl&&cntr)//匹配值
max1=max(max1,kl+kr);
if(cntl==cntr)//本就匹配
{
kl++;
kr--;
cntl=l[kl];
cntr=r[kr];
continue;
}
else if(cntl>cntr)
{
kr--;
cntl-=cntr;//优化
cntr=r[kr];
}
else if(cntr>cntl)
{
kl++;
cntr-=cntl;
cntl=l[kl];
}
}
printf("%d\n",max1);
}
printf("\n");
}
return 0;
}