题解 P1973 【[NOI2011]Noi嘉年华】
一开始看岔眼了,以为是一道水题,结果发现原来一个会场能同时举办多个活动…
显然离散化是必须的,毕竟只有相对时间有作用
然后就可以以时间来作为下标DP了
获取全局最优解比较简单,f[i][j]f[i][j]表示到ii时刻A会场举行jj场活动时B会场最多进行多少活动
转移方程很好推:
f[i][j]=max(f[k][j]+sum[k][i],f[k] [j−sum[k][i]]),k<i,j>=sum[k][i]
f[i][j]=max(f[k][j]+sum[k][i],f[k] [j−sum[k][i]]),k<i,j>=sum[k][i]
其中sumsum数组代表从[l,r][l,r]这段时间内的活动总和
maxmax函数前半部分指的是
kk时刻A会场举行jj场活动时B会场最多举 行的活动数+kk到ii这段时间内的活动数
也就是将kk到ii这段时间内的活动全在B会场举行
maxmax函数后半部分同理
表示,kk时刻A会场举行j−sum[k] [i]j−sum[k][i]场活动时B会场最多举行的活动数
也就是将kk到ii这段时间内的活动全在A会场举行
最后的答案就是max(min(j,f[n] [j]))max(min(j,f[n][j]))
然后题目还要求必定选择某个特定的活动
其实将原方程变一下
正取一个ff(pre),倒取一个ff(nxt)
pre[i][j]pre[i][j]表示从1到ii中A会 场举行jj场活动B会场能举行多少场活动
nxt[i][j]nxt[i][j]表示从ii到结尾中A会 场举行jj场活动B会场能举行多少场活动
然后枚举中间部分(num),再加上左右区间就好了
为什么程序中貌似直接将num[i][j]给了B会 场呢?其实是因为A,B会场是等价的…
可以试试看将num[i][j]直接给A会场,也是能AC的
具体看代码吧
代码如下:
#include <bits/stdc++.h> using namespace std; const int N=205; const int INF=1e9; int b[N],e[N]; int s[N<<1]; int pre[N<<1][N],nxt[N<<1][N]; int num[N<<1][N<<1]; int mus[N<<1][N<<1]; int ANS[N]; int n; int read() { int ans=0,flag=1; char ch=getchar(); while((ch<'0' || ch>'9') && ch!='-') ch=getchar(); if(ch=='-') { flag=-1;ch=getchar(); } while(ch>='0' && ch<='9') { ans=ans*10+ch-'0';ch=getchar(); } return ans*flag; } int main() { n=read(); for(int i=1;i<=n;++i) { b[i]=read(); e[i]=b[i]+read(); s[i*2-1]=b[i]; s[i*2]=e[i]; } sort(s+1,s+n*2+1); s[0]=unique(s+1,s+n*2+1)-(s+1); for(int i=1;i<=n;++i) { b[i]=lower_bound(s+1,s+s[0]+1,b[i])-s; e[i]=lower_bound(s+1,s+s[0]+1,e[i])-s; } for(int i=1;i<=s[0];++i) for(int j=i;j<=s[0];++j) for(int k=1;k<=n;++k) if(b[k]>=i && e[k]<=j) ++num[i][j]; for(int t=1;t<=s[0];++t) for(int i=1;i<=n;++i) pre[t][i]=nxt[t][i]=-1; pre[0][0]=nxt[s[0]+1][0]=0; for(int t=1;t<=s[0];++t) { for(int i=0;i<=num[1][t];++i) pre[t][i]=max(pre[t-1][i],pre[t][i]);//继承上面答案 for(int i=0;i<=num[1][t];++i) for(int j=1;j<t;++j) { if(i>=num[j][t]) pre[t][i]=max(pre[j][i-num[j][t]],pre[t][i]);//num[j][t]都由1会场拿到 if(pre[j][i]!=-1) pre[t][i]=max(pre[j][i]+num[j][t],pre[t][i]);//num[j][t]都由2会场拿到 } } for(int t=s[0];t;--t) { for(int i=0;i<=num[t][s[0]];++i) nxt[t][i]=max(nxt[t+1][i],nxt[t][i]);//继承上面答案 for(int i=0;i<=num[t][s[0]];++i) for(int j=s[0];j>t;--j) { if(i>=num[t][j]) nxt[t][i]=max(nxt[j][i-num[t][j]],nxt[t][i]);//num[t][j]都由1会场拿到 if(nxt[j][i]!=-1) nxt[t][i]=max(nxt[j][i]+num[t][j],nxt[t][i]);//num[t][j]都由2会场拿到 } } for(int i=1;i<=s[0];++i) for(int j=i+1;j<=s[0];++j) { for(int x=0,y=n;x<=n;++x) { if(pre[i][x]==-1) continue; while(y) { int val1=min(x+y,pre[i][x]+num[i][j]+nxt[j][y]); int val2=min(x+y-1,pre[i][x]+num[i][j]+nxt[j][y-1]); if(val2>=val1 || nxt[j][y]==-1) --y; else break; } mus[i][j]=max(mus[i][j],min(x+y,pre[i][x]+num[i][j]+nxt[j][y]));//必选num[i][j]到2会场的答案 //也可以改成mus[i][j]=max(mus[i][j],min(x+y+num[i][j],pre[i][x]+nxt[j][y])); //当然,如果你要改这一句的话上面val1,val2也需要改 } } for(int i=0;i<=num[1][s[0]];++i) ANS[0]=max(ANS[0],min(pre[s[0]][i],i)); for(int i=1;i<=n;++i) for(int l=b[i];l>=1;--l) for(int r=e[i];r<=s[0];++r) ANS[i]=max(ANS[i],mus[l][r]); for(int i=0;i<=n;i++) printf("%d\n",ANS[i]); return 0; }