朴素的DP线段覆盖
原题:http://codevs.cn/problem/1214/
1214 线段覆盖
题目描述 Description
给定x轴上的N(0<N<100)条线段,每个线段由它的二个端点a_I和b_I确定,I=1,2,……N.这些坐标都是区间(-999,999)的整数。有些线段之间会相互交叠或覆盖。请你编写一个程序,从给出的线段中去掉尽量少的线段,使得剩下的线段两两之间没有内部公共点。所谓的内部公共点是指一个点同时属于两条线段且至少在其中一条线段的内部(即除去端点的部分)。
输入描述 Input Description
输入第一行是一个整数N。接下来有N行,每行有二个空格隔开的整数,表示一条线段的二个端点的坐标。
输出描述 Output Description
输出第一行是一个整数表示最多剩下的线段数。
样例输入 Sample Input
3
6 3
1 3
2 5
样例输出 Sample Output
2
数据范围及提示 Data Size & Hint
0<N<100
个人解法:
其实这个题是DP好练手,不过我们可以尝试用贪心来解决。
大胆地猜想:
按照线段右端点从小到大排序,for一遍查找可行线段累加权值。
严谨地求证(其实很简单):
对于某一线段,使其右端点越小,就可以使剩下更多的区间从而容纳更多线段。
所以贪心思路就出来了。
代码如下:
type
inf=record
l,r:longint;
end;
var
n,i,right,num:longint;
a:array[1..100] of inf;
procedure swap(var a,b:longint);
var
c:longint;
begin
c:=a;
a:=b;
b:=c;
end;
procedure sort(li,ri:longint);
var
i,j,x:longint;
begin
i:=li;
j:=ri;
x:=a[(li+ri) div 2].r;
repeat
while a[i].r<x do inc(i);//右端点排序
while x<a[j].r do dec(j);
if not(i>j) then
begin
swap(a[i].r,a[j].r);
swap(a[i].l,a[j].l);
inc(i);
dec(j);
end;
until i>j;
if li<j then sort(li,j);
if i<ri then sort(i,ri);
end;
begin
read(n);
for i:=1 to n do
begin
read(a[i].l,a[i].r);
if a[i].l>a[i].r then swap(a[i].l,a[i].r);//严谨地无话可说
end;
sort(1,n);//排序
right:=-1000;
for i:=1 to n do
if a[i].l>=right then
begin
inc(num);
right:=a[i].r;
end;
writeln(num);
end.
当然这道题也可以用DP解决。
我们可以用f[i]表示到选择第i条线段的最长长度。每一条线段的权值都为1。
type
inf1=record
l,r:longint;
end;
var
w,f:array[0..1000] of longint;
a:array[1..1000] of inf1;
n,i,s,j:longint;
function max(u,v:longint):longint;
begin
if u>v then exit(u);
exit(v);
end;
procedure swap(var hy,ht:longint);
var
hr:longint;
begin
hr:=ht;
ht:=hy;
hy:=hr;
end;
procedure sort(l,r:longint);
var
vi,vj,vx:longint;
begin
vi:=l;
vj:=r;
vx:=a[(l+r) div 2].r;
repeat
while a[vi].r<vx do
inc(vi);
while vx<a[vj].r do
dec(vj);
if not(vi>vj) then
begin
swap(a[vi].r,a[vj].r);
swap(a[vi].l,a[vj].l);
swap(w[vi],w[vj]);
inc(vi);
dec(vj);
end;
until vi>vj;
if l<vj then
sort(l,vj);
if vi<r then
sort(vi,r);
end;
begin
read(n);
for i:=1 to n do
begin
read(a[i].l,a[i].r);
if a[i].l>a[i].r then swap(a[i].l,a[i].r);//读入
end;
sort(1,n);
for i:=1 to n do
f[i]:=1;
for i:=1 to n do//初始化
w[i]:=1;
for i:=1 to n do
for j:=1 to n do
if a[j].r<=a[i].l then f[i]:=max(f[i],f[j]+w[i]);//DP方程
for i:=1 to n do
if f[i]>s then s:=f[i];//这里是不可以直接用f[n]来得出。
writeln(s);
end.
其实DP思路还可以用另一种方法。
在我的代码中,f[i]表示的是当前选择i这一条线段时可以到达的最大长度。
所以动态转移方程为:
f[i]=max(f[i],f[j]+w[i])
还有一种思路就是,f[i]表示的是到i这一条线段时可以达到的最大值(这两种方法有很大区别)。
所以动态转移方程为:
f[i]=max(f[i-1],f[j]+w[i])
二者均有限制条件:a[j].r<=a[i].l
在这一题中,用第一种方法就可以AC。但是在后几题,尤其是线段覆盖4和5,对于查询的优化,就只能用第二种方法。具体情况之后再提出。
第二种方法我们可以用f[n]表示最终答案。但是第一种方法在最后必须for一遍求出答案。
原题:http://codevs.cn/problem/3027/
3027 线段覆盖 2
题目描述 Description
数轴上有n条线段,线段的两端都是整数坐标,坐标范围在0~1000000,每条线段有一个价值,请从n条线段中挑出若干条线段,使得这些线段两两不覆盖(端点可以重合)且线段价值之和最大。
n<=1000
输入描述 Input Description
第一行一个整数n,表示有多少条线段。
接下来n行每行三个整数, ai bi ci,分别代表第i条线段的左端点ai,右端点bi(保证左端点<右端点)和价值ci。
输出描述 Output Description
输出能够获得的最大价值
样例输入 Sample Input
3
1 2 1
2 3 2
1 3 4
样例输出 Sample Output
4
数据范围及提示 Data Size & Hint
数据范围
对于40%的数据,n≤10;
对于100%的数据,n≤1000;
0<=ai,bi<=1000000
0<=ci<=1000000
个人解法:
这一题用贪心就比较麻烦了(反正我是想不出)。
所以我们就只好用DP了。
我们可以发现,第一题其实是权值为1的线段覆盖2。所以,我们可以加入权值这一条件。
用f[i]表示选择第i条线段达到的最大的长度(当然也可以把f[i]作为到i为止的最长长度)。
代码如下:
type
inf1=record
l,r:longint;
end;
var
w,f:array[0..1000] of longint;
a:array[1..1000] of inf1;
n,i,s,j:longint;
function max(u,v:longint):longint;
begin
if u>v then exit(u);
exit(v);
end;
procedure swap(var hy,ht:longint);
var
hr:longint;
begin
hr:=ht;
ht:=hy;
hy:=hr;
end;
procedure sort(l,r:longint);
var
vi,vj,vx:longint;
begin
vi:=l;
vj:=r;
vx:=a[(l+r) div 2].r;
repeat
while a[vi].r<vx do
inc(vi);
while vx<a[vj].r do
dec(vj);
if not(vi>vj) then
begin
swap(a[vi].r,a[vj].r);
swap(a[vi].l,a[vj].l);
swap(w[vi],w[vj]);
inc(vi);
dec(vj);
end;
until vi>vj;
if l<vj then
sort(l,vj);
if vi<r then
sort(vi,r);
end;
begin
read(n);
for i:=1 to n do
read(a[i].l,a[i].r,w[i]);//读入
sort(1,n);//排序
for i:=1 to n do
f[i]:=w[i];//初始化
for i:=1 to n do
for j:=1 to n do
if a[j].r<=a[i].l then f[i]:=max(f[i],f[j]+w[i]);//DP方程
for i:=1 to n do
if f[i]>s then s:=f[i];//得到答案
writeln(s);
end.
原题:http://codevs.cn/problem/1643/
1643 线段覆盖 3
题目描述 Description
在一个数轴上有n条线段,现要选取其中k条线段使得这k条线段两两没有重合部分(端点可以重合),问最大的k为多少。
输入描述 Input Description
输入格式
输入文件的第1行为一个正整数n,下面n行每行2个数字ai,bi,描述每条线段。
输出描述 Output Description
输出格式
输出文件仅包括1个整数,为k的最大值
样例输入 Sample Input
3
0 2
2 4
1 3
样例输出 Sample Output
2
数据范围及提示 Data Size & Hint
数据范围
对于20%的数据,n≤10;
对于50%的数据,n≤1000;
对于70%的数据,n≤100000;
对于100%的数据,n≤1000000,0≤ai<bi≤1000000。
个人解法:
通过上两个题,我们发现:
对于有权值的我们只能 用DP来做,而对于没有权值(权值均为1)的我们便可以用贪心来做。
我们根据程序可以知道,DP的时间复杂度是O(n^2),贪心的时间复杂度是O(n)。所以,在可以用贪心时我们就尽量用贪心做。
比方说这一道题。
我们发现n的范围<=100 0000,如果我们用朴素的DP那就基本上会TLE了。所以这一个题我们用贪心会更好(至于DP,在后面的线段覆盖4会提到)。
有一个小小的提示。好像在这一题中朴素的快排有点吃力了。因为好像存在逆序和似逆序的数据。所以我们可以用random来优化快排(其实这又好像是线段覆盖4的必要做法,记不太清了,反正能优化的就优化吧)。
代码如下:
type
inf1=record
l,r:longint;
end;
var
a:array[1..1000000] of inf1;
n,i,p,j,right:longint;
function max(u,v:longint):longint;
begin
if u>v then exit(u);
exit(v);
end;
procedure swap(var hy,ht:longint);
var
hr:longint;
begin
hr:=ht;
ht:=hy;
hy:=hr;
end;
procedure sort(l,r:longint);
var
vi,vj,vx:longint;
begin
vi:=l;
vj:=r;
vx:=a[random(r-l)+l].r;//随机化优化
repeat
while a[vi].r<vx do
inc(vi);
while vx<a[vj].r do
dec(vj);
if not(vi>vj) then
begin
swap(a[vi].r,a[vj].r);
swap(a[vi].l,a[vj].l);
inc(vi);
dec(vj);
end;
until vi>vj;
if l<vj then
sort(l,vj);
if vi<r then
sort(vi,r);
end;
begin
read(n);
for i:=1 to n do
begin
read(a[i].l,a[i].r);
if a[i].l>a[i].r then swap(a[i].l,a[i].r);
end;
randomize;//很重要!
sort(1,n);
p:=0;
for i:=1 to n do
if a[i].l>=right then
begin
inc(p);
right:=a[i].r;//贪心
end;
writeln(p);
end.
原题:http://codevs.cn/problem/3012/
3012 线段覆盖 4
题目描述 Description
数轴上有n条线段,线段的两端都是整数坐标,坐标范围在0~1000000,每条线段有一个价值,请从n条线段中挑出若干条线段,使得这些线段两两不覆盖(端点可以重合)且线段价值之和最大。
输入描述 Input Description
第一行一个整数n,表示有多少条线段。
接下来n行每行三个整数, ai bi ci,分别代表第i条线段的左端点ai,右端点bi(保证左端点<右端点)和价值ci。
输出描述 Output Description
输出能够获得的最大价值
样例输入 Sample Input
3
1 2 1
2 3 2
1 3 4
样例输出 Sample Output
4
数据范围及提示 Data Size & Hint
n <= 1000000
0<=ai,bi<=1000000
0<=ci<=1000000
数据输出建议使用long long类型(Pascal为int64或者qword类型)
个人解法:
我们发现这一次必须要用到DP了,因为有权值,我们不可以简单的贪心。
可是,如果用朴素的DP又会bomb,怎么办呢?
我们想,在我们的DP中用了两个for来求最优解。第一个for是不可能优化了,但是第二个for每一次都从1到i,有点浪费时间了。
那么我们可以优化一下第二层for。
在这里,我们的DP数组f[i]表示的是到第i条线段为止可以达到的最长长度。
我们仔细想想,会发现我们要的是不与当前线段相交的某一线段可以达到的最长长度,接下来我们就可以发现,这些线段存在着单调性,也就是说,如果当前i与j都满足要求,并且i>j,那么f[i]>=f[j]。
所以我们可以用二分查找的方法快速得到我们所需要的f[j]。
DP复杂度就可以降为O(nlogn)。
还有,说两个提醒点。
一是提醒一下线段覆盖3提醒过的,这一套题都是卡快排的数据……所以,随机化优化快排(当然,也可以用平衡优化快排)。
二是题目中提醒过的,int64……
代码如下:
type
inf1=record
l,r:int64;
end;
var
w,f:array[0..10000000] of int64;
a:array[0..10000000] of inf1;
n,i,s,j:longint;
function max(u,v:int64):int64;
begin
if u>v then exit(u);
exit(v);
end;
procedure swap(var hy,ht:int64);
var
hr:int64;
begin
hr:=ht;
ht:=hy;
hy:=hr;
end;
procedure sort(l,r:longint);
var
vi,vj:longint;
vx:int64;
begin
vi:=l;
vj:=r;
vx:=a[l+random(r-l+1)].r;
repeat
while a[vi].r<vx do
inc(vi);
while vx<a[vj].r do
dec(vj);
if not(vi>vj) then
begin
swap(a[vi].r,a[vj].r);
swap(a[vi].l,a[vj].l);
swap(w[vi],w[vj]);
inc(vi);
dec(vj);
end;
until vi>vj;
if l<vj then
sort(l,vj);
if vi<r then
sort(vi,r);
end;
function find(x:longint):int64;//二分求最优情况
var
l,r,mid:longint;
begin
l:=0;
r:=x;
while l<r-1 do
begin
mid:=(l+r) div 2;
if a[mid].r>a[x].l then r:=mid
else l:=mid;
end;
exit(l);
end;
begin
read(n);
for i:=1 to n do
read(a[i].l,a[i].r,w[i]);//读入
randomize;//很重要!
sort(1,n);
for i:=1 to n do
f[i]:=max(f[i-1],f[find(i)]+w[i]);//DP方程
writeln(f[n]);
end.
我们想想线段覆盖2。
如果这个时候我们的f[i]表示选择第i条线段所得到的最大长度,那么我们就不能用二分答案来优化第二层for,因为这是不满足单调性的。所以我们应该用f[i]表示到第i条线段为止所达到的最长长度。
原题:http://codevs.cn/problem/3037/
3037 线段覆盖 5
题目描述 Description
数轴上有n条线段,线段的两端都是整数坐标,坐标范围在0~10^18,每条线段有一个价值,请从n条线段中挑出若干条线段,使得这些线段两两不覆盖(端点可以重合)且线段价值之和最大。
输入描述 Input Description
第一行一个整数n,表示有多少条线段。
接下来n行每行三个整数, ai bi ci,分别代表第i条线段的左端点ai,右端点bi(保证左端点<右端点)和价值ci。
输出描述 Output Description
输出能够获得的最大价值
样例输入 Sample Input
3
1 2 1
2 3 2
1 3 4
样例输出 Sample Output
4
数据范围及提示 Data Size & Hint
n <= 1000000
0<=ai,bi<=10^18
0<=ci<=10^9
数据输出建议使用long long类型(Pascal为int64或者qword类型)
个人解法:
那么这一题和线段覆盖4有区别吗?
其实有一点小小的区别。
我们发现n的范围并没有变,但是端点值变大了很多(其实也并没有很多),我们只需要把线段覆盖4的程序int64改成qword就可以了。
代码如下(我就不加注解了):
type
inf1=record
l,r:qword;
end;
var
w,f:array[0..10000000] of qword;
a:array[0..10000000] of inf1;
n,i,s,j:longint;
function max(u,v:qword):qword;
begin
if u>v then exit(u);
exit(v);
end;
procedure swap(var hy,ht:qword);
var
hr:qword;
begin
hr:=ht;
ht:=hy;
hy:=hr;
end;
procedure sort(l,r:longint);
var
vi,vj:longint;
vx:qword;
begin
vi:=l;
vj:=r;
vx:=a[l+random(r-l+1)].r;
repeat
while a[vi].r<vx do
inc(vi);
while vx<a[vj].r do
dec(vj);
if not(vi>vj) then
begin
swap(a[vi].r,a[vj].r);
swap(a[vi].l,a[vj].l);
swap(w[vi],w[vj]);
inc(vi);
dec(vj);
end;
until vi>vj;
if l<vj then
sort(l,vj);
if vi<r then
sort(vi,r);
end;
function find(x:longint):qword;
var
l,r,mid:longint;
begin
l:=0;
r:=x;
while l<r-1 do
begin
mid:=(l+r) div 2;
if a[mid].r>a[x].l then r:=mid
else l:=mid;
end;
exit(l);
end;
begin
read(n);
for i:=1 to n do
read(a[i].l,a[i].r,w[i]);
randomize;
sort(1,n);
for i:=1 to n do
f[i]:=max(f[i-1],f[find(i)]+w[i]);
writeln(f[n]);
end.
五个题全部AC了。不知道你们有些什么感想。其实不写题解还不会想这么多,一写题解就发现其中还有这么多的玄机。
如果各位大师还有什么优化与质疑,欢迎提出。
DP路上,希望与你们共同进步。