http://www.lydsy.com/JudgeOnline/problem.php?id=2436
这个题做起来很复杂很繁琐。。。以下思路整理自http://blog.csdn.net/whjpji/article/details/7547159
首先将所有的区间离散化,这一步很好实现。
然后就是求三个数组 num[i][j],pre[i][j],suf[i][j]
num[i][j]=[i,j] 区间内的线段个数。
pre[i][j]=[0,i] 区间内给B j 个线段,A得到最多线段个数。
suf[i][j]=[j,∞) 区间内给B j 个线段,A得到最多线段个数。
容易推出 num[i][j] 。我们枚举 i ,对于所有左端点 Lt 大于等于 i 的区间右端点 Rt ,标记 num′[i][Rt]++ ,那么显然 num[i][j]=∑jk=inum′[i][k] ,这一个求和的步骤,可以将所有的 num[i][t] 在 O(n) 时间内通过递推求前缀和的形式求出。
然后就是 pre[i][j]和suf[i][j] 了,这两个其实基本上一样,下面只讲 pre[i][j] 的求法。
然后就要求一个 g[i][j] , g[i][j]=[i,j] 区间内的所有线段必须选,使得A和B中保含线段少的那个集合里的线段个数最多多少。
在 x 固定的情况下, fx,y随y 递增呈单凸特点,因此可以利用这一性质,维护两个指针 x,y ,遍历 x ,对于每个 x ,可以在期望复杂度 O(1) 时间内,移动指针 y 并快速找到使得当前的 fx,y 最大的 y 。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#define MAXN 410
#define INF 0x3f3f3f3f
using namespace std;
struct Segment
{
int L,R;
}seg[MAXN*2];
int stack[MAXN*2],top=0,n;
int pre[MAXN][MAXN],suf[MAXN][MAXN],g[MAXN][MAXN];
int num[MAXN][MAXN];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&seg[i].L,&seg[i].R);
seg[i].R+=seg[i].L; //!!!!
stack[top++]=seg[i].L;
stack[top++]=seg[i].R;
}
sort(stack,stack+top);
top=unique(stack,stack+top)-stack;
for(int i=1;i<=n;i++)
{
seg[i].L=lower_bound(stack,stack+top,seg[i].L)-stack;
seg[i].R=lower_bound(stack,stack+top,seg[i].R)-stack;
}
for(int i=0;i<top;i++) //预处理出num[i][i]~num[i][top]
{
for(int j=1;j<=n;j++) //!!!!!
if(seg[j].L>=i)
num[i][seg[j].R]++;
for(int j=i+1;j<top;j++) //!!!!!
num[i][j]+=num[i][j-1];
}
memset(pre,~INF,sizeof(pre));
memset(suf,~INF,sizeof(suf));
pre[0][0]=0;
suf[top-1][0]=0;
for(int i=0;i<top;i++)
{
for(int j=0;j<=n;j++)
if(pre[i][j]>~INF)
pre[i][pre[i][j]]=max(pre[i][pre[i][j]],j);
for(int j=n-1;j>=0;j--)
pre[i][j]=max(pre[i][j],pre[i][j+1]);
for(int j=0;j<=n;j++)
for(int k=i+1;k<top;k++) //向后递推,[0,i]里给A放入j个区间,那么用[0,k]里在B中放入pre[i][j]个区间,在A中放入j+num[i][k]个区间的方案更新答案
if(pre[i][j]>~INF)
pre[k][pre[i][j]]=max(pre[k][pre[i][j]],j+num[i][k]);
}
for(int i=top-1;i>=0;i--)
{
for(int j=0;j<=n;j++)
if(suf[i][j]>~INF)
suf[i][suf[i][j]]=max(suf[i][suf[i][j]],j);
for(int j=n-1;j>=0;j--)
suf[i][j]=max(suf[i][j],suf[i][j+1]);
for(int j=0;j<=n;j++)
for(int k=i-1;k>=0;k--) //向前递推,[i,INF)里给A放入j个区间,那么用[k,INF)里在B中放入suf[i][j]个区间,在A中放入j+num[k][i]个区间的方案来更新答案
if(suf[i][j]>~INF)
suf[k][suf[i][j]]=max(suf[k][suf[i][j]],j+num[k][i]);
}
for(int i=0;i<top;i++) //求g[i][j]=必须使用[i,j]内的区间
for(int j=i;j<top;j++)
{
g[i][j]=~INF;
for(int x=0,y=n;x<=n;x++)
{
while(y>=0&&x+y>num[i][j]+pre[i][x]+suf[j][y]) y--; //x=[0,i]中B选择的区间个数,y=[j,INF)中B选择的区间个数
if(y>=0) g[i][j]=max(g[i][j],x+y);
}
}
int ans=0;
for(int i=0;i<=n;i++)
ans=max(ans,min(i,suf[0][i]));
printf("%d\n",ans);
for(int i=1;i<=n;i++) //!!!!
{
ans=0;
for(int j=0;j<=seg[i].L;j++)
for(int k=seg[i].R;k<top;k++)
ans=max(ans,g[j][k]);
printf("%d\n",ans);
}
return 0;
}