动态规划 学习笔记

动态规划

线性

Luogu P1541

直接枚举每种棋子使用的次数。转移时可能从四个地方转移上来,分别判断。

d[0][0][0][0]=a[1];
for(int i=0;i<=x[1];i++)for(int j=0;j<=x[2];j++)for(int k=0;k<=x[3];k++)for(int l=0;l<=x[4];l++){
    int t=a[1+i+j*2+k*3+l*4];
    if(i)d[i][j][k][l]=max(d[i][j][k][l],d[i-1][j][k][l]+t);
    if(j)d[i][j][k][l]=max(d[i][j][k][l],d[i][j-1][k][l]+t);
    if(k)d[i][j][k][l]=max(d[i][j][k][l],d[i][j][k-1][l]+t);
    if(l)d[i][j][k][l]=max(d[i][j][k][l],d[i][j][k][l-1]+t);
}

Luogu P1868

将所有区间按照右端点排序。对于每个区间,二分查找在它之前最近的那个区间。进行转移,在前一个区间和相邻区间中取最大值。

for(int i=1;i<=n;i++){
    int l=a[i].first;
    int p=lower_bound(a+1,a+1+i,make_pair(0,l),cmp)-(a+1);
    d[i]=max(d[i-1],d[p]+a[i].second-a[i].first+1);
}

YZOJ P6439

先考虑每行内的选择情况:定义 g[j][k][l] 为这一行内前 j 个数选了 k 个模为 l 的数时最大的和。

memset(g,-0x3f3f,sizeof(g)),g[0][0][0]=0;
for(int j=1;j<=m;j++){
    for(int k=0;k<=j;k++){
        for(int l=0;l<p;l++){
            g[j][k][l]=g[j-1][k][l];
            if(k)g[j][k][l]=max(g[j][k][l],g[j-1][k-1][(l-a[i][j]%p+p)%p]+a[i][j]);
        }
    }
}

接下来是行之间的转移,考虑 f[i][j] 为前 i 行中选数模为 j 的最大的和,在每行求完 g 数组后转移,需要枚举这一行内选数的数量。

for(int j=0;j<p;j++){
    for(int k=0;k<=m/2;k++){
        for(int l=0;l<p;l++){
            f[i][j]=max(f[i][j],f[i-1][(j-l+p)%p]+g[m][k][l]);
        }
    }
}

Luogu P2679

定义 f[i][j][k][1/0] 为 A 在 i,B 在 j,目前有 k 个串时的方案数,最后一维表示是否匹配。

转移:对于不匹配的,从 i-1 的两种方案转移过来,j 和 k 都不变。

如果两个字符相等,那么可以从三种情况转移:上一位不匹配、这一位新开始匹配;上一位匹配,这一位继续;上一位匹配,这一位划分新的串。否则能匹配方案数为 0。

滚掉第一维。

f[0][0][0][0]=f[1][0][0][0]=1;
for(int i=1;i<=n;i++,v^=1){
        for(int j=1;j<=m;j++){
        for(int k=1;k<=K;k++){
            f[v][j][k][0]=(f[v^1][j][k][0]+f[v^1][j][k][1])%mod;
            if(a[i]==b[j])f[v][j][k][1]=(f[v^1][j-1][k-1][0]+f[v^1][j-1][k-1][1]+f[v^1][j-1][k][1])%mod;
            else f[v][j][k][1]=0;
        }
    }
}

背包

Luogu P1156

把垃圾按照时间排序。每次可以选择增加高度或者增长生命,即 f[i][j]+=bf[i][j+a]=f[i][j]。实际上使用被动转移。

mxa 是生命值最大值,mxb 是高度最大值。若最大高度大于总高度则能爬出,否则最长存活时间就是生命最大值。

memset(f,-0x3f3f,sizeof(f)),f[0][10]=0,mxa=10;
sort(s+1,s+1+m,cmp);
for(int i=1;i<=m;i++){
    int tmp=mxa;
    for(int j=s[i].t;j<=tmp;j++){
        f[i][j]=max(f[i][j],f[i-1][j]+s[i].b),mxb=max(mxb,f[i][j]);
        f[i][j+s[i].a]=max(f[i][j+s[i].a],f[i-1][j]),mxa=max(mxa,j+s[i].a);
    }
}

Luogu U224668

二进制优化的多重背包。需要提前选出一个,把额外价值加上,其余的按二进制分组。

for(int i=1;i<=m;i++){
    int a=read(),b=read(),c=read(),d=read(),k=1;
    v[++n]=c,w[n]=b+d,a--;
    while(a>=k)v[++n]=c*k,w[n]=b*k,a-=k,k*=2;
    if(a)v[++n]=c*a,w[n]=b*a;
}
for(int i=1;i<=n;i++)for(int j=t;j>=v[i];j--)f[j]=max(f[j],f[j-v[i]]+w[i]);

区间

Luogu P3146

考虑 f[l][r] 表示区间 [l,r] 全部合并得到的值,无法合并则为 0。进行区间 dp,对于两个区间的值相同且不为 0 就更新。

初始值 f[i][i]=a[i],统计答案初值应当设为单点最大值(防止完全不能合并),在所有区间中寻找最大值([1,n] 不一定能合并)。

for(int i=1;i<=n;i++)f[i][i]=read(),ans=max(ans,f[i][i]);
for(int len=2;len<=n;len++)for(int i=1;i+len-1<=n;i++){
    int j=i+len-1;
    for(int k=i;k<j;k++)if(f[i][k]==f[k+1][j]&&f[i][k]){
        f[i][j]=max(f[i][j],f[i][k]+1),ans=max(ans,f[i][j]);
    }
}

Luogu P1063

拆环。合并的长度从 2 到 n+1(尾巴和头接)。d[i][j][i,j] 合并结果,从 d[i][k]+d[k][j] 合并,k 应当被合并两次。

细节,第三层循环时 k 取值 [i+1,j)(?)

for(int len=2;len<=n+1;len++)for(int i=1;i+len-1<=2*n;i++){
    int j=i+len-1;
    for(int k=i+1;k<j;k++){
        d[i][j]=max(d[i][j],d[i][k]+d[k][j]+a[i]*a[k]*a[j]);
        ans=max(ans,d[i][j]);
    }
}

Luogu P1005

每行操作互不影响。定义 f[i][j] 表示当前未选走的区间为 [i,j](因为选数是从两头进行的,中间必然连续)。

考虑顺推,在 [i,j] 可以取成 [i+1,j][i,j-1],转移。统计答案时要注意把最后一个数选上。

不能贪心的原因:最大的数不一定会是最后选的,例如 9 9 9 1 1 10 会考虑先把 10 选走,以便于选走三个 1,最后选 9。

for(int len=m;len>1;len--)for(int i=1;i+len-1<=m;i++){
    int j=i+len-1,t=(int)1<<(m-len+1);
    f[i+1][j]=max(f[i+1][j],f[i][j]+t*a[i]);
    f[i][j-1]=max(f[i][j-1],f[i][j]+t*a[j]);
}
for(int i=1,t=((int)1<<m);i<=m;i++)tmp=max(tmp,f[i][i]+t*a[i]);
ans+=tmp;

树形

Luogu P1122

树上 dfs,判断如果选取自己的子树是否优,更优则更新。

void dfs(int u,int fa){
    for(auto v:tv[u]){
        if(v==fa)continue;
        dfs(v,u);
        if(f[v]>0)f[u]+=f[v];
    }
    f[u]+=a[u],ans=max(ans,f[u]);
}

状态压缩

Luogu P1896

将每行的状态(只有0和1)压缩到一个 int 中。

void dfs(int x,int f,int sum){
    if(x>=n){
        sit[++ccnt]=f,g[ccnt]=sum;
        return ;
    }
    dfs(x+2,f+(1<<x),sum+1),dfs(x+1,f,sum);
}

signed main(){
    n=read(),m=read(),dfs(0,0,0);
    for(int i=1;i<=ccnt;i++)dp[1][i][g[i]]=1;
    for(int i=2;i<=n;i++)for(int j=1;j<=ccnt;j++)for(int k=1;k<=ccnt;k++){
        if((sit[j]|sit[j]<<1|sit[j]>>1)&sit[k])continue;
        for(int l=m;l>=g[j];l--)dp[i][j][l]+=dp[i-1][k][l-g[j]];
    }
    for(int i=1;i<=ccnt;i++)ans+=dp[n][i][m];
    printf("%lld",ans);
    return 0;
}

sit[i]:每行的第 i 种情况的状态(压缩);g[i]:第 i 种情况的国王数;dp[i][j][k]:第 i 行选择了第 j 种情况、直到目前摆了 k 个国王的方案数。

首先预处理每行可能合法的摆放方法(使国王不相邻的所有可能情况)。然后枚举每一行 i 的情况 j 和上一行的情况 k,若 j 和 k 不冲突则转移 dp[i][j][ ]+=dp[i-1][k][ ],其中第三维(摆的国王数量)需要在转移时枚举。

数位

YZOJ P6510

数位dp,状态同时记录 数位和 和 数位平方和。

vector<int>pr;
bool np[MAXN+1];//素数筛

int n,a[21];
int f[21][MAXN][MAXN];//记忆化
int dfs(int x,bool lmt,int sum1,int sum2){
    if(!x)return np[sum1]&&np[sum2];
    if(!lmt&&f[x][sum1][sum2]!=-1)return f[x][sum1][sum2];
    int up=lmt?a[x]:9,res=0;
    for(int i=0;i<=up;i++)res+=dfs(x-1,lmt&&(i==a[x]),sum1+i,sum2+i*i);
    if(!lmt&&sum1&&sum2)f[x][sum1][sum2]=res;
    return res;
}
int getans(int x){
    n=0;
    while(x)a[++n]=x%10,x/=10;
    return dfs(n,1,0,0);
}

signed main(){
    memset(f,-1,sizeof(f));
    int T=read();
    while(T--){
        int x=read(),y=read();
        cout<<getans(y)-getans(x-1)<<endl;
    }
    return 0;
}

优化

posted @ 2024-06-17 14:53  ofbwyx  阅读(17)  评论(0)    收藏  举报