您的位置:首页 > 其它

[BZOJ 2436][NOI 2011]NOI嘉年华(DP优化)

2015-04-24 21:42 323 查看

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=2436

思路

这个题做起来很复杂很繁琐。。。以下思路整理自/article/2926787.html

首先将所有的区间离散化,这一步很好实现。

然后就是求三个数组num[i][j],pre[i][j],suf[i][j]num[i][j],pre[i][j],suf[i][j]

num[i][j]=[i,j]num[i][j]=[i,j]区间内的线段个数。

pre[i][j]=[0,i]pre[i][j]=[0,i]区间内给B jj个线段,A得到最多线段个数。

suf[i][j]=[j,∞)suf[i][j]=[j,\infty)区间内给B jj个线段,A得到最多线段个数。

容易推出num[i][j]num[i][j]。我们枚举ii,对于所有左端点LtL_t大于等于ii的区间右端点RtR_t,标记num′[i][Rt]++num'[i][R_t]++,那么显然num[i][j]=∑jk=inum′[i][k]num[i][j]=\sum_{k=i}^jnum'[i][k],这一个求和的步骤,可以将所有的num[i][t]num[i][t]在O(n)O(n)时间内通过递推求前缀和的形式求出。

然后就是pre[i][j]和suf[i][j]pre[i][j]和suf[i][j]了,这两个其实基本上一样,下面只讲pre[i][j]pre[i][j]的求法。

pre[i][j]=max{max0≤k<i{pre[k][j]+num[k][i],pre[k][j−num[k][i]]}}pre[i][j]=\max \{ \max_{0\leq k

(注:区间[0,i][0,i]中给B放入jj个线段,可以枚举k<ik,要么在区间[0,k][0,k]中给B放入jj个线段,(k,i](k,i]这段里的所有线段都给A;要么在区间[0,k][0,k]中给B放入j−num[j][k]j-num[j][k]个线段,(k,i](k,i]这段里的所有线段都给B)

然后就要求一个g[i][j]g[i][j],g[i][j]=[i,j]g[i][j]=[i,j]区间内的所有线段必须选,使得A和B中保含线段少的那个集合里的线段个数最多多少。

g[i][j]=maxx,y∈[0,n]{min{x+y,pre[i][x]+num[i][j]+suf[j][y]}}g[i][j]=\max_{x,y\in[0,n]}\{\min \{x+y,pre[i][x]+num[i][j]+suf[j][y]\}\}



fx,y=min{x+y,pre[i][x]+num[i][j]+suf[j][y]}f_{x,y}=\min \{x+y,pre[i][x]+num[i][j]+suf[j][y]\}

xx是[i,j][i,j]左边放入B中的线段个数,yy是[i,j][i,j]右边放入B中的线段个数

在xx固定的情况下,fx,y随yf_{x,y}随y递增呈单凸特点,因此可以利用这一性质,维护两个指针x,yx,y,遍历xx,对于每个xx,可以在期望复杂度O(1)O(1)时间内,移动指针yy并快速找到使得当前的fx,yf_{x,y}最大的yy。

代码

#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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: