复习动规(1)

今天是7月13号,现在打算冲国银。

从现在到国赛前,每天会复习或学习知识点。

今天是复习动态规划。

状压DP

第一道,[NOI2001]炮兵阵地很明显也很好写的状压dp。

dp[i][j][k]是第i行状态为j,第i-1行状态为k的方案数,再枚举一重状态即可。挺经典。

第二道,[SDOI2009]Bill的挑战定义的数组稍微有点不一样。

预处理时有个辅助数组f[i][j],指第i位的字符可以为j的字符串的集合。

dp[i][j]指的是当前做到字符串第i位,满足条件的字符串的集合为j的方案数。

最后求值的时候要判断j中1的个数刚好等于k。

第三道,[NOI2015]寿司晚宴原题可转化为“将2~n中的部分数字分为两组,一个组中的所有数的质因数集合与另一个组的质因数集合无交集”。

这样我们就可以想到设dp[i][j]指A组的质因数集合为i,B组的质因数集合为j(i&j!=0)。再遍历每个数可以放入A组或B组或不放。

然后发现每个数大于22的质因数至多一个,把它拎出并用它排序,保证大于22的质因数相同的一些数在一起。

再设fa[i][j],fb[i][j],初值设为dp[i][j],定义和dp[i][j]一样,但是一个专门放入A组,一个专门放入B组。

质因数相同的一些数弄完之后,dp[i][j]=fa[i][j]+fb[i][j]-dp[i][j]。因为fa和fb是包括了dp初值的,所以dp[i][j]=专门放入A组+专门放入B组+不放入任何一组。

具体看代码实现:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,p[11]={2,3,5,7,11,13,17,19};
ll P,ans,dp[261][261],fa[261][261],fb[261][261];
struct nod{
    int s,h;
}a[510];
bool operator>(nod aa,nod bb){
    return aa.h<bb.h;
}
bool operator<(nod aa,nod bb){
    return aa.h>bb.h;
}
int main(){ int x;
    scanf("%d%lld",&n,&P);
    for(int i=2;i<=n;i++){ x=i;
        for(int j=0;j<8;j++) if(!(x%p[j])){
            a[i].s|=(1<<j);
            while(!(x%p[j])) x/=p[j];
        }
        a[i].h=x;
    }
    sort(a+2,a+1+n);
    dp[0][0]=1;
    for(int i=2;i<=n;i++){
        if(a[i].h!=a[i-1].h||a[i].h==1){
            memcpy(fa,dp,sizeof(dp));
            memcpy(fb,dp,sizeof(dp));
        }
        for(int j=255,t;j>=0;j--)
            for(int k=j;k>=0;k=(k-1)&j){ t=(j^k);//这里k是枚举j的子集,于是j^k和k无交集 
                if(!(a[i].s&k)) fa[t|a[i].s][k]=(fa[t|a[i].s][k]+fa[t][k])%P;
                if(!(a[i].s&t)) fb[t][k|a[i].s]=(fb[t][k|a[i].s]+fb[t][k])%P;
                if(!k) break;
            }
        if(a[i].h!=a[i+1].h||a[i].h==1)
            for(int j=255,t;j>=0;j--)
                for(int k=j;k>=0;k=(k-1)&j){ t=(j^k);
                    dp[t][k]=(fa[t][k]+fb[t][k]-dp[t][k]+P)%P;
                    if(!k) break;
                }
    }
    for(int j=255;j>=0;j--)
        for(int k=j;k>=0;k=(k-1)&j){
            ans=(ans+dp[j^k][k])%P;
            if(!k) break;
        }
    printf("%lld
",ans);
    return 0;
}
[NOI2015]寿司晚宴

倍增优化DP

第一道,跑路是有点水,好像和动规没关系?先把一步能到达的路连起来再floyd就完事了。

单调性优化DP

第一道,[NOI2011]NOI 嘉年华

首先将时间离散化,并设f[i][j],g[i][j],表示1~i的时间内第一个嘉年华有j个活动时的第二个嘉年华最大活动数,i~T的时间内第一个嘉年华有j个活动时的第二个嘉年华最大活动数。

再预处理d[i][j]指i~j时间内的所有活动数,即可O(n3)dp出f[i][j],g[i][j],其中max{min(j,f[T][j])}为第一个答案。

要使第i个活动必须举办,则设dp[i][j]为i~j时间内的活动必须举办的最佳答案,第i+1个答案就是max{dp[l][r]}(1<=l<=s[i],s[i]+t[i]<=r<=T)。

接下来只要求出dp[l][r]就可以了,可知dp[l][r]=max{min(x+y+d[l][r],f[l][x]+g[r][y])}。(x指l左边的在第一个嘉年华的活动数,y指r右边的在第一个嘉年华的活动数)

乍一看要O(n4),事实上发现x越大,y的最佳值越小,所以利用单调性就可以优化一重时间复杂度。对了,要注意细节。

具体看代码实现:

#include<bits/stdc++.h>
using namespace std;
int n,ct,ans,s[210],t[210],b[410],d[410][410],f[410][210],g[410][210],dp[410][410];
map<int,int>q;
int Max(int x,int y) {return x>y?x:y;}
int Min(int x,int y) {return x<y?x:y;}
int rd(){
    int s=0,ff=1;
    char w=getchar();
    while(!isdigit(w))
        ff^=w=='-',w=getchar();
    while(isdigit(w))
        s=s*10+w-'0',w=getchar();
    return ff?s:-s;
}
int main(){
    n=rd(); int y;
    for(int i=1;i<=n;i++)
        s[i]=rd(),t[i]=s[i]+rd(),b[i*2-1]=s[i],b[i*2]=t[i];
    sort(b+1,b+1+n*2);
    for(int i=1;i<=n*2;i++)
        if(b[i]!=b[i-1]||i==1)
            q[b[i]]=++ct;
    for(int i=1;i<=n;i++) s[i]=q[s[i]],t[i]=q[t[i]];
    for(int i=1;i<=ct;i++)
        for(int j=i;j<=ct;j++)
            for(int k=1;k<=n;k++)
                if(i<=s[k]&&t[k]<=j) d[i][j]++;
    memset(f,-127/3,sizeof(f));
    memset(g,-127/3,sizeof(g));
    f[1][0]=0;
    for(int i=2;i<=ct;i++)
        for(int j=0;j<=n;j++)
            for(int k=1;k<i;k++)
                f[i][j]=Max(f[i][j],Max(f[k][Max(j-d[k][i],0)],f[k][j]+d[k][i]));
    for(int j=0;j<=n;j++)
        ans=Max(ans,Min(j,f[ct][j]));
    printf("%d
",ans);
    g[ct][0]=0;
    for(int i=ct-1;i>=1;i--)
        for(int j=0;j<=n;j++)
            for(int k=i+1;k<=ct;k++)
                g[i][j]=Max(g[i][j],Max(g[k][Max(j-d[i][k],0)],g[k][j]+d[i][k]));
    for(int l=1;l<=ct;l++)
        for(int r=l+1;r<=ct;r++){ y=n;
            while(y&&g[r][y]<0) y--;
            for(int x=0;x<=n;x++){ if(f[l][x]<0) break;
                while(y&&Min(x+y+d[l][r]-1,f[l][x]+g[r][y-1])>=Min(x+y+d[l][r],f[l][x]+g[r][y])) y--;//注意这里取等号 
                dp[l][r]=Max(dp[l][r],Min(x+y+d[l][r],f[l][x]+g[r][y]));
            }
        }
    for(int i=1;i<=n;i++){ ans=0;
        for(int l=1;l<=s[i];l++)
            for(int r=t[i];r<=ct;r++)
                ans=Max(ans,dp[l][r]);
        printf("%d
",ans);
    }
    return 0;
}
[NOI2011]NOI嘉年华

今天就写了这么多,明天加油吧。

原文地址:https://www.cnblogs.com/manmanjiangQwQ/p/13293101.html