【提高组】

P1005 矩阵取数游戏 (区间DP)

区间DP。

可以看出每行互不影响,所以每次区间DP求出本行最大值,ans即加上每一行最大值。

转移方程式:f[L][R]=max(num[L]*p[k]+dp(L+1,R),dp(L,R-1)+num[R]*p[k])

#include<bits/stdc++.h>
#define For(i,l,r) for(int i=l;i<=r;i++)
#define lll __int128
#define ll long long
using namespace std;
const int M=81;
int n,m,num[M];
lll ans,p[M],f[M][M];
inline ll read(){
    ll f=1,sum=0;
    char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){sum=(sum<<1)+(sum<<3)+(ch^48);ch=getchar();}
    return f*sum;
}
inline void write(lll x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
inline lll dp(int l,int r){
    if(f[l][r]!=-1) return f[l][r];
    if(r-l>=1) f[l][r]=max(num[l]*p[m-r+l]+dp(l+1,r),dp(l,r-1)+num[r]*p[m-r+l]);
    else f[l][r]=num[l]*p[m-r+l];
    return f[l][r];
} 
int main(){
    n=read(),m=read();
    p[0]=1;
    For(i,1,m) p[i]=p[i-1]*2;
    For(i,1,n){
        For(j,1,m) num[j]=read();
        memset(f,-1,sizeof(f));
        ans+=dp(1,m);
    } 
    write(ans);
    return 0;
}
View Code

 

P1373 小a和uim之大逃离

#蒟蒻看再久都不知从何下手,神犇一眼秒说是基础递推#

一个比较有趣的dp方程,f[i][j][p][k]中(i,j)表示所处位置,p代表魔液差值,k代表最后一步是小a还是小uim走。a[i][j]即所处位置魔液值。ans加上f[i][j][0][1]。

转移方程式很好想+k是避免减出负数)

f[i][j][p][0]+=f[i-1][j][(p-a[i][j]+k)%k][1]

f[i][j][p][0]+=f[i][j-1][(p-a[i][j]+k)%k][1]

f[i][j][p][1]+=f[i-1][j][(p+a[i][j])%k][0]

f[i][j][p][1]+=f[i][j-1][(p+a[i][j])%k][0]

四维数组注意算空间内存。两次30分因为写成+=,别缩写啦!!

#include<bits/stdc++.h>
#define ll long long
#define For(i,l,r) for(int i=l;i<=r;i++)
using namespace std;
const int p=1e9+7;
int dp[805][805][20][2];
inline ll read(){
    ll f=1,sum=0;
    char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){sum=(sum<<1)+(sum<<3)+(ch^48);ch=getchar();}
    return f*sum;
}
int f[805][805][20][2],n,m,k,a[805][805],ans; 
int main(){
    scanf("%d%d%d",&n,&m,&k);++k;
    For(i,1,n){
        For(j,1,m){
            scanf("%d",&a[i][j]);
            f[i][j][a[i][j]%k][0]=1;
        }
    }
    For(i,1,n){
        For(j,1,m){
            For(h,0,k){
                f[i][j][h][0]=(f[i][j][h][0]+f[i-1][j][(h-a[i][j]+k)%k][1])%p;
                f[i][j][h][0]=(f[i][j][h][0]+f[i][j-1][(h-a[i][j]+k)%k][1])%p;
                f[i][j][h][1]=(f[i][j][h][1]+f[i-1][j][(h+a[i][j])%k][0])%p;
                f[i][j][h][1]=(f[i][j][h][1]+f[i][j-1][(h+a[i][j])%k][0])%p;
            }
        }
    }
    For(i,1,n){
        For(j,1,m){
            ans=(ans+f[i][j][0][1])%p;
        }
    }
    printf("%d
",ans);
    return 0;
}
View Code

P2279 [HNOI2003]消防局的设立(树形DP)

“树形DP!(喜形于色.jpg)”“撸完树形DP再去撸题解中的神仙贪心!”

首先F[i][state]表示节点i在状态state下的最小消防局数。

状态设计为5个

F[i][0]表示可以覆盖到从节点i向上2层的最小消防站个数

F[i][1]表示可以覆盖到从节点i向上1层的最小消防站个数(即节点i的整颗子树及其父亲)

F[i][2]表示可以覆盖到从节点i向上0层的最小消防站个数

F[i][3]表示可以覆盖到从节点i向上-1层的最小消防站个数(即节点i的子树和它们的子孙)

F[i][4]表示可以覆盖到从节点i向上-2层的最小消防站个数

为什么没有向上3层?因为不可能(要求距离在2以内)。为什么没有向上-3层?因为如果F[i][3~4]则已经把这种情况覆盖。

状态转移

F[i][0]=1+F[s][4](si的儿子

F[i][1]=min{ F[s][0](si的儿子)+ F[t][3] (ti的儿子ts} F[i][0]中的最小值

F[i][2]=min { F[s][1] (si的儿子+ F[t][2] (ti的儿子ts} F[i][0~1]中的最小值

F[i][3]={ F[s][2] si的儿子)} 和[i][0-2]中的最小值

F[i][4]={ F[s][3](si的儿子} F[i][0-3]中的最小值

答案

我们希望寻找当整棵树都被覆盖的时候的消防站最小值,于是需要覆盖到根的这一层,于是结果是F[1][2]。

代码

#include<bits/stdc++.h>
#define ll long long
#define ri register int
#define For(i,l,r) for(int i=l;i<=r;i++)
using namespace std;
const int M=1005;
const int inf=0x3f3f3f3f;
struct node{
    int to,nxt;
}e[M];
int x,n,head[M],f[M][5],cnt;
inline void add(int u,int v){
    e[++cnt].to=v;
    e[cnt].nxt=head[u];
    head[u]=cnt;
}
inline void dfs(int u){
    f[u][0]=1;
    f[u][3]=0;
    f[u][4]=0;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        dfs(v);
        f[u][0]+=f[v][4];
        f[u][3]+=f[v][2];
        f[u][4]+=f[v][3];
    }
    if(!head[u]) f[u][1]=f[u][2]=1;
    else{
        f[u][1]=f[u][2]=inf;
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            int f1=f[v][0];
            int f2=f[v][1];
            for(int j=head[u];j;j=e[j].nxt){
                if(i==j)continue;
                int s=e[j].to;
                f1+=f[s][3];
                f2+=f[s][2];
            }
            f[u][1]=min(f[u][1],f1);
            f[u][2]=min(f[u][2],f2);
        }
    }
    For(i,1,4) f[u][i]=min(f[u][i],f[u][i-1]);
}
int main(){
    scanf("%d",&n);
    For(i,2,n){
        scanf("%d",&x);
        add(x,i);
    }
    dfs(1);
    printf("%d
",f[1][2]);
    return 0;
}
View Code

 附

题解     https://www.luogu.org/blog/rickole/solution-p2279

P1220 关路灯(区间DP)

吐槽一波数据太水了,我没过样例的DP都能拿80分。

区间DP显然,F[i][j][0~1]代表区间i~j的灯都关后在i(0)或j(1)位置处时的最小耗费的电功率。

转移方程为

dp[i][j][0]== min(dp[i+1][j][0]+cal(i,i+1,i,j+1), dp[i+1[j][1]+cal(i,j,i,j+1));

dp[i][j][1]= min(dp[i][j-1][0]+cal(i,j,i-1,j), dp[i][j-1][1]+cal(j-1,j,i-1,j));

cal()函数计算从上一个区间转移过来时没关的路灯消耗的电力,为加速计算,利用前缀和找到功率之和,p[i]为功率的前缀和数组,loc[i]存储路灯位置。cal(i, ,j ,l ,r)=( loc[j] - loc[i] )*( p[n] - p[r-1]+p[l] )。

为节省计算,将j放到i外面倒着做i,避免重复计算没更新的值。(朴素想法是从中间更新到两边)。

#include<bits/stdc++.h>
#define For(i,l,r) for(int i=l;i<=r;i++)
#define Dfor(i,r,l) for(int i=r;i>=l;i--)
using namespace std;
const int M=55;
int n,c,loc[M],p[M],f[M][M][2];
inline int cal(int i,int j,int l,int r){return (loc[j]-loc[i])*(p[n]-p[r-1]+p[l]);}
int main(){
    scanf("%d%d",&n,&c);
    memset(f,0x3f3f3f,sizeof(f));
    For(i,1,n){
        int tmp;
        scanf("%d%d",&loc[i],&tmp);
        p[i]=p[i-1]+tmp;
    }
    f[c][c][1]=f[c][c][0]=0;
    For(j,c,n)
        Dfor(i,j-1,1){
            f[i][j][0]=min(f[i+1][j][0]+cal(i,i+1,i,j+1),f[i+1][j][1]+cal(i,j,i,j+1));
            f[i][j][1]=min(f[i][j-1][0]+cal(i,j,i-1,j),f[i][j-1][1]+cal(j-1,j,i-1,j));
        }
    printf("%d",min(f[1][n][0],f[1][n][1]));
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/jian-song/p/11624867.html