题解 P1973 【[NOI2011]NOI 嘉年华】
command_block · · 题解
前面的话:
这道dp优化的难度并不大,但是推导比较经典,最后的优化手段也很经典,是一道练习好题!
细节有点多,不过如果认真思考方法的话可以躲掉大部分。
自以为把最后的单调性优化讲的比较清楚
题意比较复杂,简单来说吧:
给出
第一问: 让两份中分到区间数最小的一份,尽量得到更多的区间。
第二问: 在第
分析第一问:
活动不包含开始和结束瞬间,为了方便处理,我们吧所有的区间尾先减一,就能转化成熟悉的数组占位。
显然可以把区间都离散化,设离散化之后的时间域为
有一个二维dp:设
前
(注意要初始化成-INF,边界是
为了方便处理,我们在离散化后时间轴上留一前一后两个空位。
转移:
先设
这个东西可以大力
- (1)选择一段区间放到第一份
- (2)选择一段区间放到第二份
那么答案就是
总的复杂度是
分析第二问:
如何强制选择某一个区间?我们可以先把这个区间给予某一份(那一份都行,不妨给第一份),然后把剩下的东西算出来。
详细地说:
我们要设
后
转移类似
那么,强制选择了某个区间
此外还要枚举第一份选择的区间数(前后都要枚举):
这个式子是
对于每个小区间都找一个大段来包含,我们其实做了很多重复的工作。
设
(注意这个区间两边开)
那么答案就变成
问题在于如何预处理
先贴一个暴力Code:
由于数据过水居然AC了?看来考场上也可以使用这些面向数据编程的办法,毕竟数据不好造啊……
#include<algorithm>
#include<cstdio>
#define MaxN 205
#define INF 1000000000
using namespace std;
int n;
struct Line
{int l,r;}l[MaxN];
int xx[MaxN*2],tot;
int f[MaxN*2][MaxN],g[MaxN*2][MaxN];
int c[MaxN*2][MaxN*2],s[MaxN*2][MaxN*2];
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++){
scanf("%d%d",&l[i].l,&l[i].r);
l[i].r+=l[i].l-1;
xx[++tot]=l[i].l;
xx[++tot]=l[i].r;
}sort(xx+1,xx+tot+1);
for (int i=1;i<=n;i++){
l[i].l=lower_bound(xx+1,xx+tot+1,l[i].l)-xx+1;
l[i].r=lower_bound(xx+1,xx+tot+1,l[i].r)-xx+1;
}tot+=2;
for (int i=1;i<=tot;i++)
for (int j=i;j<=tot;j++)
for (int k=1;k<=n;k++)
if (i<=l[k].l&&l[k].r<=j)
c[i][j]++;
for (int i=0;i<=tot+1;i++)
for (int j=0;j<=n;j++)
f[i][j]=g[i][j]=-INF;
f[0][0]=g[tot+1][0]=0;
for (int i=1;i<=tot;i++)
for (int j=0;j<=n;j++)
for (int k=0,sav;k<i;k++){
sav=c[k+1][i];
if (j>=sav)f[i][j]=max(f[i][j],f[k][j-sav]);
f[i][j]=max(f[i][j],f[k][j]+sav);
}
int ans=0;
for (int i=0;i<=n;i++)
ans=max(ans,min(f[tot][i],i));
printf("%d\n",ans);
for (int i=tot;i;i--)
for (int j=0;j<=n;j++)
for (int k=i+1,sav;k<=tot+1;k++){
sav=c[i][k-1];
if (j>=sav)g[i][j]=max(g[i][j],g[k][j-sav]);
g[i][j]=max(g[i][j],g[k][j]+sav);
}
for (int i=1;i<=tot;i++)
for (int j=i+2;j<=tot;j++){
s[i][j]=-INF;
for (int k=0;k<=n;k++){
if (f[i][k]<0)break;
for (int t=0;t+k<=n;t++){
if (g[j][t]<0)break;
s[i][j]=max(s[i][j],
min(f[i][k]+g[j][t],k+t+c[i+1][j-1])
);
}
}
}
for (int t=1;t<=n;t++){
ans=0;
for (int i=1;i<l[t].l;i++)
for (int j=l[t].r+1;j<=tot;j++)
ans=max(ans,s[i][j]);
printf("%d\n",ans);
}return 0;
}
再看一遍式子:
我们发现这个
那么,对于一种分法,把两份的内容交换又能得到另一种,所以会在
也就是说,我们直接认为
(前面讲了
现在的目标就是尽量让左边变大(在维持右边大的情况下)
我们发现,
那么,再
有了这个单调性,预处理的复杂度就变为了
Code:
貌似只比暴力快了一倍……
#include<algorithm>
#include<cstdio>
#define MaxN 205
#define INF 1000000000
using namespace std;
int n;
struct Line
{int l,r;}l[MaxN];
int xx[MaxN*2],tot;
int f[MaxN*2][MaxN],g[MaxN*2][MaxN];
int c[MaxN*2][MaxN*2],s[MaxN*2][MaxN*2];
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++){
scanf("%d%d",&l[i].l,&l[i].r);
l[i].r+=l[i].l-1;
xx[++tot]=l[i].l;
xx[++tot]=l[i].r;
}sort(xx+1,xx+tot+1);
for (int i=1;i<=n;i++){
l[i].l=lower_bound(xx+1,xx+tot+1,l[i].l)-xx+1;
l[i].r=lower_bound(xx+1,xx+tot+1,l[i].r)-xx+1;
}tot+=2;
for (int i=1;i<=tot;i++)
for (int j=i;j<=tot;j++)
for (int k=1;k<=n;k++)
if (i<=l[k].l&&l[k].r<=j)
c[i][j]++;
for (int i=0;i<=tot+1;i++)
for (int j=0;j<=n;j++)
f[i][j]=g[i][j]=-INF;
f[0][0]=g[tot+1][0]=0;
for (int i=1;i<=tot;i++)
for (int j=0;j<=n;j++)
for (int k=0,sav;k<i;k++){
sav=c[k+1][i];
if (j>=sav)f[i][j]=max(f[i][j],f[k][j-sav]);
f[i][j]=max(f[i][j],f[k][j]+sav);
}
int ans=0;
for (int i=0;i<=n;i++)
ans=max(ans,min(f[tot][i],i));
printf("%d\n",ans);
for (int i=tot;i;i--)
for (int j=0;j<=n;j++)
for (int k=i+1,sav;k<=tot+1;k++){
sav=c[i][k-1];
if (j>=sav)g[i][j]=max(g[i][j],g[k][j-sav]);
g[i][j]=max(g[i][j],g[k][j]+sav);
}
for (int i=1;i<=tot;i++)
for (int j=i+2;j<=tot;j++){
s[i][j]=-INF;
for (int k=0,sav,t=n;k<=n;k++){
if (f[i][k]<0)break;
while(t>=0){
sav=min(f[i][k]+g[j][t],k+t+c[i+1][j-1]);
if (sav>=s[i][j])s[i][j]=sav;
else break;
t--;
}t++;
}
}
for (int t=1;t<=n;t++){
ans=0;
for (int i=1;i<l[t].l;i++)
for (int j=l[t].r+1;j<=tot;j++)
ans=max(ans,s[i][j]);
printf("%d\n",ans);
}return 0;
}