题解 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
",ANS[i]);
    return 0;
}      
 
原文地址:https://www.cnblogs.com/Sworddust/p/11427805.html