洛谷P1973 [NOI2011]Noi嘉年華(動態規劃,決策單調性)

洛谷題目傳送門算法

DP題怕是都要大大的腦洞。。。。。。數組

首先,時間那麼大沒用,直接離散化。spa

第一問還好。根據題意容易發現,當一堆活動的時間有大量重疊的時候,更好的辦法是把它們所有安排到一邊去。那麼咱們轉移的時候也確定是要一塊一塊地轉移啦。code

\(tot_{l,r}\)爲徹底被包含在\(l-r\)時間內活動總數,直接\(O(n^3)\)暴力求就行了。隊列

\(pre_{i,j}\)爲時間\(1-i\)內一邊選\(j\)個時,另外一邊能選的最大值。枚舉一塊轉移的話,咱們的方程應該寫成這樣:get

\[pre_ {i,j}=\max\limits_{k=1}^i\{pre_ {k,j}+tot_{k,i},pre _{k,j-tot _{k,i}}\}\]it

(顯然兩種狀況都要考慮)io

而後答案就是\(\max\limits_{j=1}^n\{\min(pre_{m,j},j)\}\)啦(\(m\)爲離散化後的時間總長,不會超過\(2n\)class

這個數組爲何要叫\(pre\)呢?這是個前綴DP值。爲了第二問,咱們還要作個後綴DP,\(suf_{i,j}\)表示時間\(i-m\)內一邊選\(j\)個時,另外一邊能選的最大值,跟\(pre\)幾乎同樣的轉移,也是\(O(n^3)\)的。im

對於第二問,咱們顯然能夠確定\(s_i-t_i\)以內的活動都被一邊選走了。至於\(s_i\)以前和\(t_i\)之後選了多少,咱們也只好枚舉。設\(f_{l,r}\)爲一邊強制選\(l-r\)之間全部活動時最優的最小值,假定這一邊在前面選了\(x\)個,在後面選了\(y\)個,另外一邊最多能選多少也就知道了,有方程

\[f_{l,r}=\max\limits_{x=1}^m\max\limits_{y=1}^m\{\min(x+tot_{l,r}+y,pre_{l,x}+suf_{r,y})\}\]

而後第\(i\)個的答案就是\(f_{s_i,t_i}\)麼?注意千萬別掉入這個誤區!\(pre\)\(suf\)只是保證了局部最優,而沒有保證全局最優。要說人話的話,就是可能有一個活動跨過了\(s_i\),然而\(f_{s_i,t_i}\)並無統計到它,只有擴大強制選的區間使得可以包含它,才能統計到最優解。因而須要枚舉強制選區間了,\(ans_i=\max\limits_{l=1}^{s_i}\max\limits_{r=t_i}^m\{f_{l,r}\}\)

這樣的話,整個\(f\)都必需要算出來,上面的枚舉算法就變成\(O(n^4)\)了,跑不動。

點開標籤發現有單調隊列?!蒟蒻就往單調性上面想了想,因而就有了一個結論:設枚舉\(x\)時有一個使答案最優的\(y\),那麼當\(x\)增大時,若是\(y\)也增大那麼答案不會更優。觀察上面那個式子\(\min(x+tot_{l,r}+y,pre_{l,x}+suf_{r,y})\),那麼由於\(pre,suf\)都是遞減的,因此很顯然咱們不能讓\(x,y\)變大而\(pre,suf\)變小。

因而,實現的時候,只要把\(y\)從大往小掃了,並不須要什麼單調隊列來維護它。

#include<cstdio>
#include<algorithm>
#define RG register
#define R RG int
#define G c=getchar()
#define Upd(A,L,R)     {chkmx(A[i][j],A[k][j]+tot[L][R]);   \
        if(j>=tot[L][R])chkmx(A[i][j],A[k][j-tot[L][R]]);}
#define Calc(y) min(x+tot[l][r]+y,pre[l][x]+suf[r][y])
using namespace std;
const int N=209,M=409,INF=1e9;
int s[N],t[N],b[M],tot[M][M],pre[M][N],suf[M][N],f[M][M];
inline int in(){
    RG char G;
    while(c<'-')G;
    R x=c&15;G;
    while(c>'-')x=x*10+(c&15),G;
    return x;
}
inline int min(R x,R y){return x<y?x:y;}
inline void chkmx(R&x,R y){if(x<y)x=y;}
int main(){
    R n=in(),m=0,i,j,k,l,r,x,y,p0,p1,ans;
    for(i=1;i<=n;++i){
        b[++m]=s[i]=in();
        b[++m]=t[i]=in()+s[i];
    }
    sort(b+1,b+m+1);//離散化
    m=unique(b+1,b+m+1)-b-1;
    for(i=1;i<=n;++i){
        s[i]=lower_bound(b+1,b+m+1,s[i])-b;
        t[i]=lower_bound(b+1,b+m+1,t[i])-b;
        for(l=1;l<=s[i];++l)//tot暴力求
            for(r=m;r>=t[i];--r)++tot[l][r];
    }
    for(i=1;i<=m;++i)//注意初始化
        for(j=1;j<=n;++j)pre[i][j]=suf[i][j]=-INF;
    for(i=1;i<=m;++i)
        for(j=0;j<=tot[1][i];++j)
            for(k=1;k<=i;++k)Upd(pre,k,i);
    for(i=m;i;--i)//轉移很類似,搞了個宏定義
        for(j=0;j<=tot[i][m];++j)
            for(k=i;k<=m;++k)Upd(suf,i,k);
    for(l=1;l<=m;++l)
        for(r=l+1;r<=m;++r)
            for(y=n,x=0;x<=n;++x){
                p0=Calc(y);//p0爲最優決策,p1爲當前決策
                while(y&&p0<=(p1=Calc(y-1)))p0=p1,--y;
                chkmx(f[l][r],Calc(y));
            }
    ans=0;
    for(j=1;j<=n;++j)chkmx(ans,min(pre[m][j],j));
    printf("%d\n",ans);
    for(i=1;i<=n;++i){
        ans=0;
        for(l=1;l<=s[i];++l)
            for(r=m;r>=t[i];--r)chkmx(ans,f[l][r]);
        printf("%d\n",ans);
    }
    return 0;
}
相關文章
相關標籤/搜索