DP 听课笔记

OSU!

之前做过。

[春季测试 2023] 圣诞树

好题。因为三角形两边和大于第三边,所以路径不交叉一定比路径交叉优。那么考虑区间 DP。首先断环为链,设 \(dp_{l,r,0/1}\) 表示当前已经走完了区间 \([l,r]\),当前在区间最左/右侧。同时记录 \(pre_{l,r,0/1}\) 表示对应状态的 \(dp\) 是从上一步的左/右侧转移过来的。输出直接递归或用栈就可以了。注意:由于坐标可以为负,所以一定注意边界条件。

code
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
constexpr int N=1e3+10;
int n,s;
struct Node{
    double x,y;
    int id;
}node[N<<1];
double dp[N<<1][N<<1][2],ans;
int pre[N<<1][N<<1][2];
inline double dis(const Node &a,const Node &b){
    double x=a.x-b.x,y=a.y-b.y;
    return sqrt(x*x+y*y);
}
void print(int l,int r,int p){
    if(l==r){
        printf("%d ",node[l].id);
        return;
    }
    if(p){
        print(l,r-1,pre[l][r][p]);
        printf("%d ",node[r].id);
    }
    else{
        print(l+1,r,pre[l][r][p]);
        printf("%d ",node[l].id);
    }
}
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n;
    node[s].y=-0x7fffffff;
    for(int i=1;i<=n;i++){
        cin>>node[i].x>>node[i].y;
        node[i].id=i;
        node[i+n]=node[i];
        if(node[i].y>node[s].y) s=i;
    }
    for(int i=1;i<=(n<<1);i++)
        for(int j=i;j<=(n<<1);j++)
            dp[i][j][0]=dp[i][j][1]=0x7fffffff;
    dp[s][s][0]=dp[s][s][1]=dp[s+n][s+n][0]=dp[s+n][s+n][1]=0;
    for(int len=2;len<=n;len++)
        for(int l=1,r=l+len-1;r<=(n<<1);l++,r++){
            //0 left ,1 right
            double a=dp[l+1][r][0]+dis(node[l],node[l+1]),b=dp[l+1][r][1]+dis(node[l],node[r]);
            if(a>b) dp[l][r][0]=b,pre[l][r][0]=1;
            else dp[l][r][0]=a,pre[l][r][0]=0;
            a=dp[l][r-1][1]+dis(node[r-1],node[r]),b=dp[l][r-1][0]+dis(node[l],node[r]);
            if(a>b) dp[l][r][1]=b,pre[l][r][1]=0;
            else dp[l][r][1]=a,pre[l][r][1]=1;
        }
    ans=0x7fffffff;
    for(int i=1;i<=n;i++)
        ans=min({ans,dp[i][i+n-1][0],dp[i][i+n-1][1]});
    for(int i=1;i<=n;i++){
        if(dp[i][i+n-1][0]==ans){
            print(i,i+n-1,0);
            return 0;
        }
        if(dp[i][i+n-1][1]==ans){
            print(i,i+n-1,1);
            return 0;
        }
    }
    return 0;
}

[JSOI2018] 潜入行动

树上背包。朴素的状态不足以表达信息时就考虑加状态。\(dp_{u,i,0/1,0/1}\) 表示以 \(u\) 为根的子树中,放了 \(i\) 个监视器,点 \(u\) 放/不放,点 \(u\) 是/否被覆盖。转移时涉及滚动数组,所以我们记 \(temp_{i,0/1,0/1}\) 表示当前的 \(dp_{u,i,0/1,0/1}\)
转移有点麻烦:

\[dp_{u,i+j,0,0}=\sum temp_{i,0,0}\times dp_{v,j,0,1} \]

\[dp_{u,i+j,1,0}=\sum temp_{i,1,0}\times dp_{v,j,0,0/1} \]

\[dp_{u,i+j,0,1}=\sum temp_{i,0,0}\times dp_{v,j,1,1}+temp_{i,0,1}\times dp_{v,j,0/1,1} \]

\[dp_{u,i+j,1,1}=\sum temp_{i,1,0}\times dp_{v,j,1,0/1}+temp_{i,1,1}\times dp_{v,j,0/1,0/1} \]

code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
template<typename T>
inline void read(T &x){
    bool f=0;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
typedef long long ll;
constexpr int mod=1e9+7,N=1e5+10;
struct modint{
    int val;
    modint(int val=0):val(val){}
    modint operator+(const modint &x)const{return modint((val+x.val)%mod);}
    modint &operator+=(const modint &x){val+=x.val;val%=mod;return *this;}
    modint operator*(const modint &x)const{return modint((ll)val*x.val%mod);}
};
vector<int> e[N];
int n,k,siz[N];
modint dp[N][110][2][2],temp[110][2][2];
void dfs(int u,int fa){
    dp[u][1][1][0]=dp[u][0][0][0]=1;
    siz[u]=1;
    for(int v:e[u]){
        if(v==fa) continue;
        dfs(v,u);
        for(int i=0;i<=k;i++){
            temp[i][0][0]=dp[u][i][0][0];
            temp[i][0][1]=dp[u][i][0][1];
            temp[i][1][0]=dp[u][i][1][0];
            temp[i][1][1]=dp[u][i][1][1];
            dp[u][i][0][0]=dp[u][i][0][1]=dp[u][i][1][0]=dp[u][i][1][1]=0;
        }
        for(int i=0;i<=min(siz[u],k);i++)
            for(int j=0;j<=min(siz[v],k-i);j++){
                dp[u][i+j][0][0]+=temp[i][0][0]*dp[v][j][0][1];
                dp[u][i+j][1][0]+=temp[i][1][0]*(dp[v][j][0][0]+dp[v][j][0][1]);
                dp[u][i+j][0][1]+=temp[i][0][0]*dp[v][j][1][1]+
                temp[i][0][1]*(dp[v][j][0][1]+dp[v][j][1][1]);
                dp[u][i+j][1][1]+=temp[i][1][0]*(dp[v][j][1][0]+dp[v][j][1][1])+
                temp[i][1][1]*(dp[v][j][0][0]+dp[v][j][0][1]+dp[v][j][1][0]+dp[v][j][1][1]);
            }
        siz[u]+=siz[v];
    }
}
int main(){
    read(n,k);
    for(int i=1,u,v;i<n;i++){
        read(u,v);
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs(1,0);
    printf("%d\n",(dp[1][k][0][1]+dp[1][k][1][1]).val);
    return 0;
}

AT_dp_e Knapsack 2

背包变形。将 \(v\)\(w\) 互换一下就行。

code
#include<cstdio>
#include<cstring>
template<typename T>
inline void read(T &x){
    bool f=0;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
template<typename T>
inline T Min(const T &a,const T &b){return a<b?a:b;}
constexpr int N=1e5+10;
typedef long long ll;
int n,W,w,v,sum;
ll dp[N];
int main(){
    read(n,W);
    memset(dp,0x3f,sizeof(dp));
    dp[0]=0;
    for(int i=1;i<=n;i++){
        read(w,v);
        sum+=v;
        for(int j=sum;j>=v;j--)
            dp[j]=Min(dp[j],dp[j-v]+w);
    }
    for(int i=sum;i;i--)
        if(dp[i]<=W){
            printf("%d\n",i);
            return 0;
        }
    return 0;
}

AT_dp_j Sushi

好题。\(dp_{i,j,k}\) 表示有 \(i\) 个装 \(1\) 个寿司,\(j\) 个装 \(2\) 个寿司和 \(k\) 个装 \(3\) 个寿司的盘子的期望次数。\(dp_{i,j,k}\) 为以下四项之和:

  • \(\dfrac{n-(i+j+k)}{n}\times (dp_{i,j,k}+1)\)(空盘子)

  • \(\dfrac{i}{n}\times (dp_{i-1,j,k}+1)\)(放 \(1\) 个)

  • \(\dfrac{j}{n}\times (dp_{i+1,j-1,k}+1)\)(放 \(2\) 个)

  • \(\dfrac{k}{n}\times (dp_{i,j+1,k-1}+1)\)(放 \(3\) 个)

整理,得

\[dp_{i,j,k}=\frac{n}{i+j+k}+\frac{i\times dp_{i-1,j,k}}{i+j+k}+\frac{j\times dp_{i+1,j-1,k}}{i+j+k}+\frac{k\times dp_{i,j+1,k-1}}{i+j+k} \]

显然,应当按 \(k-j-i\) 顺序枚举以消除后效性。边界:\(dp_{0,0,0}=0\)

code
#include<cstdio>
template<typename T>
inline void read(T &x){
    bool f=0;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
constexpr int N=310;
int n,cnt[4];
double dp[N][N][N];
int main(){
    read(n);
    for(int i=1,a;i<=n;i++) read(a),++cnt[a];
    for(int k=0;k<=n;k++)
        for(int j=0;j<=n;j++)
            for(int i=0;i<=n;i++){
                if(i==j&&j==k&&k==0) continue;
                double inv=i+j+k;
                dp[i][j][k]=n/inv;
                if(i) dp[i][j][k]+=dp[i-1][j][k]*i/inv;
                if(j) dp[i][j][k]+=dp[i+1][j-1][k]*j/inv;
                if(k) dp[i][j][k]+=dp[i][j+1][k-1]*k/inv;
            }
    printf("%.10lf",dp[cnt[1]][cnt[2]][cnt[3]]);
    return 0;
}
posted @ 2025-06-18 20:04  headless_piston  阅读(9)  评论(0)    收藏  举报