ACwing提高课-DP(区间dp、数位dp)

一、区间DP

区间dp经典思想

  1. 经典区间dp的设计思路:
    • d p [ i ] [ j ] dp[i][j] dp[i][j]:在区间[i,j]的最值。
    • 三重循环:
      • 一维:循环长度
      • 二维:循环左端点
      • 三维:循环分界点
    • 转移方程: d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i ] [ k ] + d p [ k + 1 ] [ r ] + w [ l ] [ r ] ) dp[i][j] = max(dp[i][j], dp[i][k] + dp[k+1][r] + w[l][r]) dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][r]+w[l][r])

1、环形石子合并

  1. 经典区间dp问题,不再赘述。
  2. ACcode
#include 
using namespace std;
typedef long long ll;
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, a, n) for (int i = n; i >= a; i--)
#define lowbit(x) ((x) & -(x))
#define lson p << 1, l, mid
#define rson p << 1 | 1, mid + 1, r
#define mem(a, b) memset(a, b, sizeof(a))
#define IOS                      \
    ios::sync_with_stdio(false); \
    cin.tie(0);                  \
    cout.tie(0)
/*
环形经典优化方式:将环拆分成长度为n的链,在将其后接入条长度为n的链
*/
const int N = 205;
int a[N * 2];
int sum[N * 2];
int dp_max[N * 2][N * 2];
int dp_min[N * 2][N * 2];
int n;
int main()
{
    IOS;
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        a[n + i] = a[i];
    }
    for (int i = 1; i <= n * 2; i++)
        sum[i] = sum[i - 1] + a[i];
    memset(dp_max, -0x3f, sizeof(dp_max));
    memset(dp_min, 0x3f, sizeof(dp_min));
    for (int len = 1; len <= n; len++) //区间长度
    {
        for (int l = 1; l + len - 1 <= n * 2; l++) //枚举左端点
        {
            int r = l + len - 1;
            if (len == 1)
                dp_max[l][r] = dp_min[l][r] = 0;
            else
            {
                for (int k = l; k < r; k++) //枚举分界点
                {
                    dp_min[l][r] = min(dp_min[l][k] + dp_min[k + 1][r] + sum[r] - sum[l - 1], dp_min[l][r]);
                    dp_max[l][r] = max(dp_max[l][k] + dp_max[k + 1][r] + sum[r] - sum[l - 1], dp_max[l][r]);
                }
            }
        }
    }
    int ans_max = 0, ans_min = INT_MAX;
    for (int l = 1; l + n - 1 <= n * 2; l++)
    {
        int r = l + n - 1;
        ans_max = max(ans_max, dp_max[l][r]);
        ans_min = min(ans_min, dp_min[l][r]);
    }
    cout << ans_min << "\n"
         << ans_max;
    return 0;
}

二、数位DP

数位dp经典思想

  1. 一般的问题描述为:
    • 给的区间 [ x , y ] [x,y] [x,y],让你找到符合一定性质数的个数。
    • 在区间中,满足性质 [ x , y ] − > d p [ y ] − d p [ x − 1 ] [x,y]->dp[y]-dp[x-1] [x,y]>dp[y]dp[x1]
    • 从数的角度考虑问题:
      • 一种情况为填写 [ 0   x − 1 ] [0~x-1] [0 x1],另一种为 x x x

1、度的数量

  1. 题目简述:一个数的b进制下,恰好有k个1,问区间中符合条件数的个数。
  2. 题解:
    1. 预处理出组合数,和n的b进制下的信息。
    2. 对于每一位,分为选择0和1的情况。
      • 如果选择0,则后面的其余位填某些1
      • 如果选择1,则后面的其余位填某些1,当b进制下当前位x>1时,break,代表当前位不能选择x。
      • 最后的分支,如果走到最后一位,且其余位已经填写了k个1,答案再加1
  3. ACcode
#include 
using namespace std;
typedef long long ll;
#define IOS                      \
    ios::sync_with_stdio(false); \
    cin.tie(0);                  \
    cout.tie(0)
const int N = 35;
int C[N][N];
int l, r, k, b;
void init()
{
    for (int i = 0; i < N; i++)
    {
        for (int j = 0; j <= i; j++)
        {
            if (j == 0)
                C[i][j] = 1;
            else
                C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
        }
    }
}

int dp(int n)
{
    if (n == 0)
        return 0;
    vector<int> ve;
    while (n) //计算数位
    {
        ve.push_back(n % b);
        n /= b;
    }
    int res = 0;
    int last_1 = 0; //前面取了多少1
    int len = ve.size();
    for (int i = len - 1; i >= 0; i--)
    {
        int x = ve[i];
        if (x > 0)
        {
            res += C[i][k - last_1]; //左边填写0,其余位填1
            if (x > 1)
            {
                if (k - last_1 - 1 >= 0)
                    res += C[i][k - last_1 - 1]; //左边填写1,其余位填0
                break;                           //
            }
            else//当前位置是1
            {
                last_1++;
                if(last_1 > k) break;
            }
        }
        if(i == 0 && last_1 == k) res++; //最右侧方案
    }
    return res;
}
int main()
{
    IOS;
    init();
    cin >> l >> r >> k >> b;
    cout << dp(r) - dp(l - 1) << "\n";
    return 0;
}

2、Windy数

  1. 首先预处理出Windy数,然后再数位dp求。
  2. ACcode
#include 
using namespace std;
typedef long long ll;
#define IOS                      \
    ios::sync_with_stdio(false); \
    cin.tie(0);                  \
    cout.tie(0)
const int N = 15;
ll f[N][12];
void init()// f[i][j]表示前i位,当前位为j的windy数的个数
{
    for (int i = 1; i <= 9; i++)
        f[1][i] = 1;
    for (int i = 2; i < N; i++)
    {
        for (int j = 0; j <= 9; j++)
        {
            for (int k = 0; k <= 9; k++)
            {
                if (abs(j - k) >= 2)
                    f[i][j] += f[i - 1][k];
            }
        }
    }
}
ll dp(int x)
{
    if (x == 0)
        return 0;
    vector<int> ve;
    while (x)
        ve.push_back(x % 10), x /= 10;
    ll res = 0;
    int len = ve.size();
    int last = 999;
    for (int i = len - 1; i >= 0; i--)
    {
        int num = ve[i];
        for (int j = (i == len - 1); j < num; j++) //不处理前导0
        {
            if (abs(last - j) >= 2)
                res += f[i + 1][j];
        }
        if (abs(last - num) < 2)
            break;
        last = num;
        if (!i)
            res++;
    }
    for (int i = 1; i < len; i++) //处理有前导0,第i+1位是0
    {
        for (int j = 1; j <= 9; j++)
        {
            res += f[i][j];
        }
    }
    return res;
}
int main()
{
    IOS;
    init();
    int l, r;
    cin >> l >> r;
    cout << dp(r) - dp(l - 1);
    return 0;
}

你可能感兴趣的:(#,ACM&基础dp,动态规划)