動態規劃專題

Abnormal123發表於2024-04-07

動態規劃專題

A.Helping People

神仙機率題

容易發現給定的區間限制滿足樹形關係,考慮建樹。

個人認為最難理解的一點是,期望最大值並不是簡單相加,所以直接設期望DP是很難做的。

設 $ a[i] $ 表示原來第 \(i\) 個人的錢數, $ dp[i][j] $ 表示第 \(i\) 個節點最大錢數小於等於 $ max{ a_k (k在區間i的範圍之內) }+j $ 的機率。注意這裡用小於等於,方便DP,最後統計答案再查分一下就好。

下面我們用 $ m[i] $ 代表 \(i\) 區間原有錢數的最大值, $ p[i] $ 代表該區間的接受機率。

可以得到狀態轉移方程

\[dp_{u,i}= p[u]\times \Pi _{v \in son_u } dp_{v,i+m[u]-m[v]-1} +(1-p[u])\times \Pi _{v\in son_u}dp_{v,i+m[u]-m[v]} \]

仔細理解一下這個方程,然後注意邊界就好。複雜度 $ O(q^2) $

Code

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+100,M=5e3+100;
struct vjudge{
    int l,r,len,id,m;
    double p;
}sug[M];
struct edge{
    int to,next;
}e[M];
int n,q,a[N],cnt,head[M],depth[M],nleaf[M];
double dp[M][M],ans;
bool f[M];
bool cmp(vjudge a,vjudge b){
    return a.len<b.len;
}
inline double Max(double x,double y){
	if(x>y)return x;
	else return y;
}
void add(int u,int v){
    cnt++;
    e[cnt].to=v;
    e[cnt].next=head[u];
    head[u]=cnt;
}
void dfs(int u,int fa){
    dp[u][0]=1-sug[u].p;
    depth[u]=depth[fa]+1;
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to;
        dfs(v,u);
    }
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to;
        dp[u][0]*=dp[v][sug[u].m-sug[v].m];
    }
    for(int i=1;i<=q;i++){
        double x=1,y=1;
        for(int j=head[u];j;j=e[j].next){
            int v=e[j].to;
            if(i+sug[u].m-sug[v].m>q)continue;
            x*=dp[v][min(i+sug[u].m-sug[v].m-1,q)];
            y*=dp[v][min(i+sug[u].m-sug[v].m,q)];
        }
        dp[u][i]=sug[u].p*x+(1-sug[u].p)*y;
    }
}
int main()
{
    scanf("%d%d",&n,&q);
    int mm=0;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        mm=max(mm,a[i]);
    }
    for(int i=1;i<=q;i++){
        scanf("%d%d%lf",&sug[i].l,&sug[i].r,&sug[i].p);
        sug[i].len=sug[i].r-sug[i].l+1;
        sug[i].id=i;
        int maxn=0;
        for(int j=sug[i].l;j<=sug[i].r;j++){
            maxn=max(maxn,a[j]);
        }
        sug[i].m=maxn;
    }
    sort(sug+1,sug+q+1,cmp);
    for(int i=1;i<=q;i++){
        for(int j=i+1;j<=q;j++){
            if(sug[i].l>=sug[j].l&&sug[i].r<=sug[j].r){
                add(j,i);
                nleaf[j]=1;
                f[i]=1;
                break;
            }
        }
    }
    ++q;
    sug[q]=(vjudge){1,n,n,q,mm,0};
    for(int i=1;i<q;i++){
        if(!f[i]){
            add(q,i);
        }
    }
    dfs(q,q);
    for(int i=0;i<q;i++){
    	if(i==0)ans+=dp[q][i]*(i+sug[q].m);
    	else 
    	ans+=(dp[q][i]-dp[q][i-1])*(i+sug[q].m);
	}
    printf("%0.9lf",ans);
}

B.Birds

簡單揹包

設 $ dp[i][j] $ 為走到第 \(i\) 棵樹,抓了 \(j\) 只鳥剩餘的最多魔法值。顯然有:

\[dp[i][j]=\max \{dp[i-1][j-k]-cost[i]\times k+X \} \]

注意兩個邊界。

  1. $ dp[i-1][j-k]<0 $ 一定要跳過,不然可能產生錯解。

  2. $ dp[i-1][j-k]+X>W+b\times k $ 此時超出魔法上限。

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e3+10;
const int M=1e4+10;
int n,w,b,x,c[N],cost[N],dp[N][M];
int sum[N];
    
signed main()
{
    scanf("%d%d%d%d",&n,&w,&b,&x);
    for(int i=1;i<=n;i++){
        scanf("%lld",&c[i]);
        sum[i]=c[i]+sum[i-1];
    }
    for(int i=1;i<=n;i++){
        scanf("%lld",&cost[i]);
    }
    memset(dp,128,sizeof(dp));
    for(int i=0;i<=c[1];i++){
        dp[1][i]=w-cost[1]*i;
    }
    for(int i=1;i<=n;i++){
        for(int j=0;j<=c[i];j++){
            for(int k=0;k<=sum[i-1];k++){
                if(dp[i-1][k]<0)continue;
                if(dp[i-1][k]+x>w+b*k)
                    dp[i][k+j]=max(dp[i][k+j],w+b*k-cost[i]*j);
                else dp[i][k+j]=max(dp[i][k+j],dp[i-1][k]-cost[i]*j+x);
            }
        }
    }
    for(int i=1;i<=sum[n]+1;i++){
        if(dp[n][i]<0){
            printf("%lld\n",i-1);
            return 0;
        }
    }
}

C.Positions in Permutations

我的做法可能看起來比較噁心,但本質上是一樣的。

設 $ dp[i][j][x][y] $ 表示前 \(i\) 個數中選擇 \(j\) 個作為好點,\(i-1\)\(i\) 的狀態,其中0代表不是好點,1代表選擇前一個位置,2代表選擇後一個位置,這樣可以得到一坨方程。(見程式碼

$ sum[i][j] $ 表示前 \(i\) 個位置,有 \(j\) 個好點的方案數,將空白位置全排列,得到的是至少有 \(j\) 個好點的方案數。

在利用二項式反演求解。複雜度 $ O(n^2) $

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1100;
const int mod=1e9+7;
int n,p,dp[N][N][3][3];
int sum[N][N];
int jc[N],ny[N];
inline int C(int x,int y){
    if(x<y)return 0;
    return jc[x]*ny[y]%mod*ny[x-y]%mod;
}
signed main()
{
    scanf("%lld%lld",&n,&p);
    jc[0]=1;
    for(int i=1;i<N;i++){
        jc[i]=jc[i-1]*i%mod;
    }
    ny[0]=1;
    ny[1]=1;
    for(int i=2;i<N;i++){
        ny[i]=(mod-mod/i)*ny[mod%i]%mod;
    }
    for(int i=2;i<N;i++){
        ny[i]=ny[i-1]*ny[i]%mod;
    }
    sum[1][0]=dp[1][0][0][0]=1;
    if(n!=1)
    sum[1][1]=dp[1][1][0][2]=1;
    for(int i=2;i<=n;i++){
        sum[i][0]=dp[i][0][0][0]=1;
        for(int j=1;j<=i;j++){
            dp[i][j][0][0]=(dp[i-1][j][0][0]+dp[i-1][j][1][0]+dp[i-1][j][2][0])%mod;
            dp[i][j][1][0]=(dp[i-1][j][0][1]+dp[i-1][j][1][1]+dp[i-1][j][2][1])%mod;
            dp[i][j][2][0]=(dp[i-1][j][0][2]+dp[i-1][j][1][2]+dp[i-1][j][2][2])%mod;
            dp[i][j][0][1]=(dp[i-1][j-1][0][0]+dp[i-1][j-1][1][0])%mod;
            dp[i][j][1][1]=(dp[i-1][j-1][0][1]+dp[i-1][j-1][1][1])%mod;
            dp[i][j][2][1]=(dp[i-1][j-1][0][2]+dp[i-1][j-1][1][2])%mod;
            if(i!=n){
                dp[i][j][0][2]=(dp[i-1][j-1][0][0]+dp[i-1][j-1][1][0]+dp[i-1][j-1][2][0])%mod;
                dp[i][j][1][2]=(dp[i-1][j-1][0][1]+dp[i-1][j-1][1][1]+dp[i-1][j-1][2][1])%mod;
                dp[i][j][2][2]=(dp[i-1][j-1][0][2]+dp[i-1][j-1][1][2]+dp[i-1][j-1][2][2])%mod;
            }
            sum[i][j]=(sum[i][j]+dp[i][j][1][1])%mod;
            sum[i][j]=(sum[i][j]+dp[i][j][1][2])%mod;
            sum[i][j]=(sum[i][j]+dp[i][j][2][1])%mod;
            sum[i][j]=(sum[i][j]+dp[i][j][2][2])%mod;
            if(i>=j+1){
                sum[i][j]=(sum[i][j]+dp[i][j][0][1])%mod;
                sum[i][j]=(sum[i][j]+dp[i][j][0][2])%mod;
                sum[i][j]=(sum[i][j]+dp[i][j][1][0])%mod;
                sum[i][j]=(sum[i][j]+dp[i][j][2][0])%mod;
                if(i>=j+2)
                sum[i][j]=(sum[i][j]+dp[i][j][0][0])%mod;
            }
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=0;j<=i;j++){
            sum[i][j]=(sum[i][j]*jc[i-j])%mod;
        }
    }
    int ans=0;
    for(int i=p,j=1;i<=n;i++,j=-j){
        ans=(ans+j*sum[n][i]*C(i,p)%mod+mod)%mod;
    }
    cout<<ans<<endl;
}

H.Tavas in Kansas

題目背景讓人感覺很紙張。

神奇博弈論

轉換題面+一些技巧

  1. 我們最後想要的是兩者得分的相對大小,由於兩個人的得分不好同時維護,不妨將兩人分數做差。

  2. 兩人位置是不變的,跑最短路,距離離散化,將距離抽象成一個二維平面,以A的距離為橫座標,以B的距離為縱座標,這樣每個城市都對映到平面上。

  3. $ dp[i][j][0/1] $ 表示當前狀態下 A行動(0)或B行動(1),這樣最後結果不好確定,所以考慮倒序DP。

透過以上操作,我們發現題目簡單多了,每次A會按照座標按行取數,B按列取數,但不能沒取到。 $ O(n^2)DP $ 即可。

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2200,M=2e5+100;
const int inf=2e9;
int n,m,s,t,p[N],cnt,head[M],ns,nt,dp[N][N][2];
int dis[2][N];
int vals[N],valt[N],ds[N],dt[N];
int num[N][N][2],siz[N][N][2];
struct edge{
    int to,next,dis;
}e[M];
struct node{
    int id,dis;
    bool operator<(const node &x)const{
        return dis>x.dis;
    }
};
priority_queue<node>q;
struct froms{
    int id,dis;
}diss[N];
struct fromt{
    int id,dis;
}dist[N];
bool cmp3(froms x,froms y){
    return x.dis<y.dis;
}
bool cmp4(fromt x,fromt y){
    return x.dis<y.dis;
}
void add_edge(int u,int v,int c){
    cnt++;
    e[cnt].to=v;
    e[cnt].next=head[u];
    e[cnt].dis=c;
    head[u]=cnt;
}
void dijkstra(int begin,int times){
    dis[times][begin]=0;
    q.push(node{begin,0});
    while(!q.empty()){
        node x=q.top();q.pop();
        for(int i=head[x.id];i;i=e[i].next){
            int y=e[i].to;
            if(dis[times][y]>x.dis+e[i].dis){
                dis[times][y]=x.dis+e[i].dis;
                q.push(node{y,dis[times][y]});
            }
        }
    }
}
signed main()
{
    memset(dis,0x3f,sizeof(dis));
    scanf("%lld%lld",&n,&m);
    scanf("%lld%lld",&s,&t);
    for(int i=1;i<=n;i++){
        scanf("%lld",&p[i]);
    }
    int u,v,w;
    for(int i=1;i<=m;i++){
        scanf("%lld%lld%lld",&u,&v,&w);
        add_edge(u,v,w);
        add_edge(v,u,w);
    }
    dijkstra(s,0);
    dijkstra(t,1);
    for(int i=1;i<=n;i++){
        diss[i]=froms{i,dis[0][i]};
        dist[i]=fromt{i,dis[1][i]};
        
    }
    sort(diss+1,diss+n+1,cmp3);
    sort(dist+1,dist+n+1,cmp4);
    int tot=1;
    vals[1]=p[diss[1].id];
    ds[s]=1;
    for(int i=2;i<=n;i++){
        if(diss[i].dis==diss[i-1].dis){
            vals[tot]+=p[diss[i].id];
            ds[diss[i].id]=tot;
        }
        else{
            tot++;
            vals[tot]+=p[diss[i].id];
            ds[diss[i].id]=tot;
        }
    }
    ns=tot;

    tot=1;
    valt[1]=p[dist[1].id];
    dt[t]=1;
    for(int i=2;i<=n;i++){
        if(dist[i].dis==dist[i-1].dis){
            valt[tot]+=p[dist[i].id];
            dt[dist[i].id]=tot;
        }
        else{
            tot++;
            valt[tot]+=p[dist[i].id];
            dt[dist[i].id]=tot;
        }
    }
    nt=tot;
    for(int i=1;i<=n;i++){
        num[ds[i]][dt[i]][0]+=p[i];
        num[ds[i]][dt[i]][1]+=p[i];
        siz[ds[i]][dt[i]][0]++;
        siz[ds[i]][dt[i]][1]++;
    }
    for(int i=0;i<=ns;i++){
        for(int j=nt;j>=0;j--){
            num[i][j][0]+=num[i][j+1][0];
            siz[i][j][0]+=siz[i][j+1][0];
        }
    }
    for(int i=0;i<=nt;i++){
        for(int j=ns;j>=0;j--){
            num[j][i][1]+=num[j+1][i][1];
            siz[j][i][1]+=siz[j+1][i][1];
        }
    }
    for(int i=ns;i>=0;i--){
        for(int j=nt;j>=0;j--){
            if(i==ns&&j==nt)continue;
            if(i!=ns){
                if(siz[i+1][j+1][0]){
                    dp[i][j][0]=max(dp[i+1][j][0],dp[i+1][j][1])+num[i+1][j+1][0];
                }
                else dp[i][j][0]=dp[i+1][j][0];
            }
            if(j!=nt){
                if(siz[i+1][j+1][1]){
                    dp[i][j][1]=min(dp[i][j+1][0],dp[i][j+1][1])-num[i+1][j+1][1];
                }
                else dp[i][j][1]=dp[i][j+1][1];
            }
        }
    }
    if(dp[0][0][0]==0)puts("Flowers");
    if(dp[0][0][0]>0)puts("Break a heart");
    if(dp[0][0][0]<0)puts("Cry");
}

I.Game on Sum (Easy Version)

相關文章