摘要
贪心、证明
链接
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=4213
hooray!
恭喜我 A A 掉了这道题,用时 4h 4 h
先来证明一个定理
定理
p
p
:按照进行排序所得到的方案,一定使得最大罚时最小。
证明:
对于任意两个元素,设
d1<d2
d
1
<
d
2
,
当
1
1
在前时,罚时为,当然最后要和
0
0
取,但这不影响我们的证明
当
2
2
在前时,罚时为,
上述只是说了两个元素相邻的情况,如果不相邻的话也是一样,同样的方法即可证明
既然对于任意的两组,
d
d
比较小的都应该在前面,那么对于整个序列,就应该是递增的,这样以来对于任意的一对元素,都使得罚时最小,所以整个序列的最大罚时最小。
然后看这道题
先按照为第一关键字,
si
s
i
为第二关键字排序(可以自己举几个例子看看为什么要以
si
s
i
为第二关键字排序),先扫描一遍整个序列,记下最大罚时的位置和最小罚时的位置,序号较小的为
p1
p
1
,序号较大的为
p2
p
2
,这样序列被分成了三个区间
[1,p1)
[
1
,
p
1
)
、
(p1,p2)
(
p
1
,
p
2
)
、
(p2,n]
(
p
2
,
n
]
,为了方便,我叫他们
A,B,C
A
,
B
,
C
根据定理
p
p
(上述所证定理),三个区间内最大罚时已经是最小了,如果我随便更改一个区间内部的顺序,得到新序列的最大罚时只会更大,如果这个数值大于了我所记录的
p1,p2
p
1
,
p
2
处的罚时,就出现了一种新的解,而这组解比原先的情况更劣,所以说,在
A,B,C
A
,
B
,
C
三个区间内部打乱顺序无助于寻找最优解。
我把
[0,p2),(p1,n]
[
0
,
p
2
)
,
(
p
1
,
n
]
分别记为
D,E
D
,
E
,如果这两个区间的顺序被扰乱,同样只可能会构造出更劣的解,不可能构造出更优的解。
那么现在只有一种方法可以构造出更优的解,就是改变
A∪C
A
∪
C
中元素的顺序,且一定不能只是扰乱一个区间的顺序。
考虑如何扰乱,我当然可以暴力枚举所有的排列顺序,但是显然不现实。
我们不是有定理
p
p
吗?
那么可以想到,只有那些满足扰乱后里的元素的
di
d
i
都单调增的排列才有用。
那么就可以枚举每个元素在哪个区间里,这样是指数级别的,也不行
看看还有没有别的线索,如果我把一个
di
d
i
比较大的从
C
C
扔到里去,那么对于这个元素来讲,它的罚时变小了,而对于
p1,p2
p
1
,
p
2
来讲,罚时都变大了,这种解是更劣的,那如果我把好多
C
C
里的元素扔到里去,显然答案会变得更劣更劣,因此
C
C
中的元素没必要放到里去。
那么
A
A
中的元素可以放到里吗?如果放一个过去的话,显然它本身的罚时变大了,而且肯定比原先的最大值还大,而原先的最大值变小了,甚至可能比它前面的某些元素还要小,因此最大值和次大值的和可能变小,可以更新一下
ans
a
n
s
。
考虑我往下扔更多的元素,在
p2
p
2
这个位置上,是我扔下来的
di
d
i
最小的元素(因为已经排序了),由于这个位置的
si
s
i
的前缀和没有改变,而
di
d
i
却变小了,所以新得到的这个位置的罚时比原来的最大罚时还大(也有可能不变,但不会变小),原先的最大值,即使现在是次大值,整个的解也不会变得更优,更何况原来的最大值现在可能会被更大的数值取代,因此不会出现更优解。
经过以上论述,排序后唯一能够构造出更优解的方法,就是从
[1,p1]
[
1
,
p
1
]
(端点可以单独验证一下)中选出一个元素,放到
p2
p
2
后面。
将
[1,p1]
[
1
,
p
1
]
中的每个元素都尝试一下,最终一定能够得到最优解。
代码
//̰ÐÄ
#include <cstdio>
#include <algorithm>
#include <cctype>
#include <cstring>
#define cl(x) memset(x,0,sizeof(x))
#define maxn 550
#define inf 0x3f3f3f3f
using namespace std;
struct task
{
int s, d, c;
}tsk[maxn], list1[maxn], list2[maxn];
int n, p1, p2, max1, max2, ans;
int read(int x=0)
{
char c, f=1;
for(c=getchar();!isdigit(c);c=getchar())if(c=='-')f=-1;
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-48;
return f*x;
}
bool cmp(task &a, task &b){return a.d==b.d?a.s<b.s:a.d<b.d;}
int calc(int &p1, int &p2)
{
int cost, i;
max1=max2=0;
for(i=1;i<=n;i++)
{
tsk[i].c=tsk[i-1].c+tsk[i].s;
cost=max(0,tsk[i].c-tsk[i].d);
if(cost>=max1)p2=p1, max2=max1, p1=i, max1=cost;
else if(cost>max2)p2=i, max2=cost;
}
}
void input()
{
int i, cost, j, k;
n=read();
for(i=1;i<=n;i++)tsk[i].s=read(), tsk[i].d=read();
sort(tsk+1,tsk+n+1,cmp);
}
void solve()
{
int i, p1, p2, x, y, j, c, size1=0, size2=0, tot;
task tmp;
calc(p1, p2);
if(p1>p2)swap(p1,p2);
ans=max1+max2;
for(i=1;i<=p1;i++)
{
tmp=tsk[i];
for(j=i;j<p2;j++)tsk[j]=tsk[j+1];
tsk[p2]=tmp;
calc(x,y);
ans=min(ans,max1+max2);
for(j=p2;j>i;j--)tsk[j]=tsk[j-1];
tsk[i]=tmp;
}
}
int main()
{
int T;
for(T=read();T--;)
{
ans=inf;
input();
solve();
printf("%d\n",ans);
}
return 0;
}