时限:1s;内存限制:162MB
这真的是一道非常非常好的题,我做了很久很久。
在网上只能搜到只有结论的贪心题解和一篇关于这道题的非常简略的论文,导致我一直想不通为什么,不过还是很感谢论文里提供的思路,虽然我并不能看懂。。
论文名称叫作《浅谈信息学竞赛中的区间问题》,下文中将多有引用。
接下来我将详细地阐述和证明关于这道题的一些结论和做法,希望可以给以后做这些的人一些启发。
一、模型转换
虽然并没有什么本质用处,但为我们思考提供了很大方便。
每个挂缀实际上等价于一个区间,W是它的长度,C是至右左端点,将这些区间排列在数轴上,区间只能在端点处相交。
这样这个奇怪的问题就变成了我们所熟悉的问题。
二、前提结论
至少存在一个最优解,使得区间是按W+C顺序排列的。
假如说最优解的区间序列中存在一个关于W+C的逆序对,不妨设其右端点分别为
Ri
,
Rj
,且
Ri<Rj,Wi+Ci>Wj+Cj
,又因为
Ri≤Wi+Ci,Rj≤Wj+Cj
,所以
Rj<Wi+Ci
,即交换i,j序列依然合法,且不会对答案产生任何影响。
三、30分算法。
容易想到一个 O(N2) 的动态规划。将区间按 Ci+Wi 的值从小到大排序,设 f(i,j) ,为前i个区间,选取了j个区间后,最大右端点坐标的最小值。则对于任意一个合法的 f(i,j) ,做下面的两个动态转移:
f(i,j)=min{
f(i−1,j), //第i个区间不选
(f(i−1,j−1)+Wi)[f(i−1,j−1)≤Ci] //选第i个区间。
}
状态数为 O(N2) ,状态转移时间为 O(1) ,故时间复杂度为 O(N2) 。然而仅仅 O(N2) 是不够的,还需要优化。
——《浅谈信息学竞赛中的区间问题》
四、满分算法。
先来看看论文里是怎么讲的:
平衡树/块链优化DP
设 g(i,j)=f(i,j)−f(i,j−1) ,显然i不变时, g(i,j) ,随着j的增大而呈单调递增(如果 g(i,j)<g(i,j−1) ,那么 f(i,j−1) 肯定不是最大右端点坐标的最小值)。故每次从 g(i) 递推 g(i+1) 时,可以进行如下的两个操作:⑴找到两个位置, left=min{j|g(i,j)>Wi+1},right=max{j|f(i,j)≤Ci+1} ;⑵删除 g(right+1) ,将 g(left+1),g(left+2),...,g(right) 的值向后平移到 g(left+2),g(left+3),...,g(right+1) ,并将 g(left+1) 的值修改为 Wi+1 。贪心
维护一个按 Wi+Ci 排序的有序表,初始为空。将所有区间按长度 Wi 从小到大排序,依次处理。处理某个区间时,若它能够放入有序表,则选择该区间并放入表中,否则不选择。最后的表即是要求的状态。
然而我比较傻逼,并没有看懂这是为什么,所以我按照我的思路来解释这个题。
按照傻逼惯用的思路,我首先打个表,发现在30分的DP中,如果f(i,j)是通过第二种转移(选择当前区间)转移过来的,那么
∀k≥j∩f(i,j)≠+∞=>f(i,k)=f(i−1,j−1)+Wi
证明:
不妨采用数学归纳法。
开始的时候反正DP状态都是空集各种性质都好说,也就是说从自己能推出自己就OK了。
这个结论有一个显然的推论是它将导致g(i,j)不减,因为从g(i,j)的角度看它相当于是在最小的j使得
g(i,j)>Wi
前插入
Wi
,然后删掉最后一项,如果最大的不是+∞的
f(i,j)>Ci
的话。
设k为最大的j使得f(i-1,j)≠+∞。
若
f(i−1,k−1)>Ci
,显然
f(i−1,k)≤Ci+Wi
,所以
g(i−1,k)<Wi
,即不存在
g(i−1,j)>Wi
,当前区间无法被插入。
若
f(i−1,k−1)≤Ci
,既然g(i-1,j)不减,那么结论就是显然成立的了。
所以当f(i-1)符合结论的时候,f(i)也符合,命题得证。
虽然我上面的证明中说了很多显然。。但是其实我想了非常久,因为我比较傻逼,所以其实很多地方。。想到了比较显然,想不到还是非常不显然的!
这样的话我们就可以发现论文中的做法明显扯淡了,right根本就不用求。
而我们也就有另一个维护g(i)的方法了,注意到这一坨g(i)其实就是W,所以我们只需要把区间按
Wi+Ci
排序,然后插入
Wi
,如果当前的和大于
Wi+Ci
,就弹出当前最大值。
考虑如果
Wi
大于当前最大值,那么显然它就直接被弹出来了,在DP中它就是
f(i−1,k−1)>Ci
的情况;如果不是这样的话,我们就把
Wi
插入进去,然后如果
f(i−1,k)>Ci
,就不能转移,也就是弹出最大值。
那么论文中的贪心是怎么回事呢?
其实与DP是等价的。
考虑我们刚才是怎么维护的。
其实就相当于是把论文中的贪心用线段树维护搞成了扫描线+堆,弹出最大者,就相当于是说在论文的贪心做法中,这个家伙会发现自己插不进去了。
五、需要注意的问题
1、一定要拍大数据!
2、这个题。。数据不知道怎么回事,从题面上看W和C都应该会爆int的。。
#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;
#include<algorithm>
int heap[200005];
pair<long long,unsigned> a[200005];
char * cp=(char *)malloc(4000000);
inline void in(int &x){
while(*cp<'0'||*cp>'9')++cp;
x=0;
while(*cp>='0'&&*cp<='9')x=x*10+(*cp++^'0');
}
inline void in(long long &x){
while(*cp<'0'||*cp>'9')++cp;
x=0;
while(*cp>='0'&&*cp<='9')x=x*10+(*cp++^'0');
}
inline void in(unsigned &x){
while(*cp<'0'||*cp>'9')++cp;
x=0;
while(*cp>='0'&&*cp<='9')x=x*10+(*cp++^'0');
}
int main(){
freopen("pendant.in","r",stdin);
freopen("pendant.out","w",stdout);
fread(cp,1,4000000,stdin);
int N,i,now,next,L=0;
long long W=0;
in(N);
for(i=N;i--;){
in(a[i].first),in(a[i].second);
a[i].first+=a[i].second;
}
sort(a,a+N);
for(i=0;i<N;){
heap[++L]=a[i].second;
for(now=L,next=now>>1;next&&heap[now]>heap[next];now=next,next>>=1)swap(heap[now],heap[next]);
W+=a[i].second;
for(++i;i<N&&a[i].first-a[i].second<W;++i)
if(a[i].second<heap[1]){
W=W+a[i].second-heap[1];
heap[1]=a[i].second;
for(now=1,next=now<<1;next<=L;now=next,next<<=1){
if(next<L&&heap[next|1]>heap[next])++next;
if(heap[now]>=heap[next])break;
swap(heap[now],heap[next]);
};
}
}
printf("%d\n%lld\n",L,W);
}