动态规划练习

原洛谷博客

## AT_dp_j

dp$_{i,j,k}$表示剩i个一个寿司的,j个两个寿司的,k个三个寿司的概率

$$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) + \dfrac{j}{n} \times (dp_{i + 1, j - 1, k} + 1) + \dfrac{k}{n} \times (dp_{i, j + 1, k - 1} + 1) = \dfrac{n - i - j - k}{n} \times dp_{i, j, k} + \dfrac{i}{n} \times dp_{i - 1, j, k} + \dfrac{j}{n} \times dp_{i + 1, j - 1, k} + \dfrac{k}{n} \times dp_{i, j + 1, k - 1} + 1$$

移项。

$$\dfrac{i + j + k}{n} \times dp_{i, j, k} = \dfrac{i}{n} \times dp_{i - 1, j, k} + \dfrac{j}{n} \times dp_{i + 1, j - 1, k} + \dfrac{k}{n} \times dp_{i, j + 1, k - 1} + 1$$

整理得转移方程。

$$dp_{i, j, k} = \dfrac{i}{i + j + k} \times dp_{i - 1, j, k} + \dfrac{j}{i + j + k} \times dp_{i + 1, j - 1, k} + \dfrac{k}{i + j + k} \times dp_{i, j + 1, k - 1} + \dfrac{n}{i + j + k}$$


```cpp
#include<bits/stdc++.h>
using namespace std;
double f[305][305][305];
int a,b,c,n;
int main(){
  cin>>n;
  for(int i=1;i<=n;i++){
    int x;
    cin>>x;
    if(x==1){
      a++;
    }else if(x==2){
      b++;
    }else{
      c++;
    }
  }
  for(int k=0;k<=n;k++){
    for(int j=0;j<=n;j++){
      for(int i=0;i<=n;i++){
        if(i==0&&j==0&&k==0) continue;
        f[i][j][k]=1.0*n/(i+j+k);
        if(i!=0) f[i][j][k]+=1.0*(i)/(i+j+k)*f[i-1][j][k];
        if(j!=0) f[i][j][k]+=1.0*(j)/(i+j+k)*f[i+1][j-1][k];
        if(k!=0) f[i][j][k]+=1.0*(k)/(i+j+k)*f[i][j+1][k-1];
      }
    }
  }
  printf("%.10lf",f[a][b][c]);
  return 0;
}
```

---

## AT_dp_o

$dp[i][j]$(1<=$i$<=n)(0<=$j$<(1<<$n$-1))表示选取前i个男生对应匹配的女生集合为j时的方案数


```cpp
#include<bits/stdc++.h>
using namespace std;
long long f[2][5000005],mod=1000000007;
int n,e[25][25],p;
int main(){
  cin>>n;
  for(int i=1;i<=n;i++){
    for(int j=1;j<=n;j++){
      cin>>e[i][j];
    }
  }
  f[p][0]=1;
  for(int i=1;i<=n;i++){
    for(int j=0;j<(1<<(n));j++){
      int cnt=__builtin_popcount(j);
      if(cnt!=i) continue;
      for(int k=0;k<n;k++){
        if(e[i][k+1]&&((1<<k)&j)!=0){
          f[p^1][j]=(f[p^1][j]+f[p][j^(1<<k)])%mod;
        }
      }
    }
    p=1-p;
  }
  cout<<f[p][(1<<n)-1];
  return 0;
}
```

---

## AT_dp_r

$f[t][i][j]$为$i$到$j$长度为$t$的路径的方案数

$f[t][i][j]=\sum_{i=1}^{n}f[t-x][i][k]*f[x][k][j]$

发现x取任何值都会得到相同的答案,所以x取1时

$f[t][i][j]=\sum_{i=1}^{n}f[t-1][i][k]*f[1][k][j]$

=$\sum_{i=1}^{n}f[t-1][i][k]*a[k][j]$

发现该式子和矩乘一样,可以愉快的矩乘加速


```cpp
#include<bits/stdc++.h>
using namespace std;
int n;
long long a[55][55],b[55][55],c[55][55],k,mod=1000000007;
void ch(long long x[][55],long long y[][55]){
  for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) c[i][j]=0;
  for(int i=1;i<=n;i++){
    for(int j=1;j<=n;j++){
      for(int k=1;k<=n;k++){
        c[i][j]=(c[i][j]+x[i][k]*y[k][j])%mod;
      }
    }
  }
}
void qpow(long long y){
  while(y){
    if(y%2==1){
      ch(b,a);
      for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) b[i][j]=c[i][j];
    }
    ch(a,a);
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) a[i][j]=c[i][j];
    y/=2;
  }
}
int main(){
  cin>>n>>k;
  for(int i=1;i<=n;i++){
    b[i][i]=1;
    for(int j=1;j<=n;j++){
      cin>>a[i][j];
    }
  }
  qpow(k);
  long long ans=0;
  for(int i=1;i<=n;i++){
    for(int j=1;j<=n;j++){
      ans=(ans+b[i][j])%mod;
    }
  }
  cout<<ans;
  return 0;
}
```

---

## [AT_dp_z](https://www.luogu.com.cn/problem/AT_dp_z)

斜率优化

$dp[i]$表示到第$i$格的最短费用

不难写出转移方程:

$dp[i]=\sum_{j=1}^{j=i-1}dp[j]+(h[i]-h[j])^2+C$ 

O(n^2)可以求出

考虑化简

$dp[i]=dp[j]+h[i]^2+h[j]^2+C-2*h[i]*h[j]$

对于$j<k$要使从$j$转移更优,需满足

$dp[j]+h[i]^2+h[j]^2+C-2*h[i]*h[j]<dp[k]+h[i]^2+h[k]^2+C-2*h[i]*h[k]$

化简得

$dp[j]+h[j]^2-2*h[i]*h[j]<dp[k]+h[k]^2-2*h[k]*h[k]$

$-2*h[i]*(h[j]-h[k])<dp[k]+h[k]^2-h[j]^2-dp[j]$

$h[i]\geq\frac{dp[j]+h[j]^2-h[k]^2-dp[k]}{2*(h[j]-h[k])}$

此时不等式的左边类似于求斜率的公式,故称为斜率优化。

---

对于优先队列里的$i<j<k$

$j$是优的,当且仅当$slope(i,j)\geq h[i]且slope(i,j)\leq h[i]$

既$slope(i,j)\geq slope(i,j)$

```cpp
#include<bits/stdc++.h>
using namespace std;
long long n,c,h[200005],q[200005],head=1,tail=1,f[200005];
double slope(int x,int y){
  return (double)(h[x]*h[x]-h[y]*h[y]+f[x]-f[y])/((double)(h[x]-h[y])*2);
}
int main(){
  cin>>n>>c;
  for(int i=1;i<=n;i++){
    cin>>h[i];
  }
  q[1]=1;
  f[1]=0;
  for(int i=2;i<=n;i++){
    while(head<tail&&slope(q[head+1],q[head])<=h[i]) head++;
    f[i]=f[q[head]]+(h[i]-h[q[head]])*(h[i]-h[q[head]])+c;
    while(head<tail&&slope(q[tail],q[tail-1])>=slope(i,q[tail])) tail--;
    q[++tail]=i;
  }
  cout<<f[n];
  return 0;
}
```

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值