ACM: Poj 1769(DP+线段树)

题目大意

在这里插入图片描述

纯DP

  • 这道题是动态规划+线段树的题,我们先给出超时的动态规划解法,再给出利用线段树的优化解法。
  • 由于只是给了一个 n n n值,没给数列实际情况,因此我们按最坏情况打算,即最大值在第一个。

下面是动态规划操作。

  • (1): 定义 d p [ i ] [ j ] dp[i][j] dp[i][j]: 在前 i i i个机器可选条件下,将最大数移动到第 j j j个位置所使用的最少机器数量。
  • (2): 目标 d p [ m ] [ n ] dp[m][n] dp[m][n]: 即在所有机器可选条件下,将最大值移动到最后一位所需最少机器数量。
  • (3): 状态转移方程式:
    d p [ i ] [ j ] = { d p [ i − 1 ] [ j ] t [ i ] ! = j m i n { d p [ i − 1 ] [ j ] , m i n { d p [ i − 1 ] [ j ′ ] ∣ s i &lt; = j ′ &lt; = t i } } e l s e dp[i][j] = \begin{cases} dp[i-1][j] &amp; t[i] !=j \\ min\{dp[i-1][j], min\{dp[i-1][j&#x27;]|s_i &lt;=j&#x27;&lt;=t_i\}\} &amp; else \end{cases} dp[i][j]={dp[i1][j]min{dp[i1][j],min{dp[i1][j]si<=j<=ti}}t[i]!=jelse
    • s i , t i s_i, t_i si,ti:分别是第 i i i台机器负责的的起始和结束位置。
    • t [ i ] ! = j t[i] != j t[i]!=j时: 如果最大值在第 i i i台机器负责的区间内,那么我们不能选择该机器,如果不在负责区间内,选择该机器也没用。因此我们舍弃该机器。
      在这里插入图片描述
    • t [ i ] = j t[i] = j t[i]=j时: 我们可以选择或不选择该机器,若不选择,则为 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j], 若选择,则需要前 i − 1 i-1 i1个机器将最大值移到了 s [ i ] &lt; = j &lt; = t [ i ] s[i]&lt;=j&lt;=t[i] s[i]<=j<=t[i]之间。
  • (4): 更新策略:由于 i i i递增,因此按照 i i i由小到大更新。
  • (5): 初始化: d p [ 0 ] [ ∗ ] = i n f , d p [ 0 ] [ 1 ] = 0 dp[0][*] = inf, dp[0][1] = 0 dp[0][]=inf,dp[0][1]=0 , 我们假想最大值在第一个位置,因此不使用机器可以将最大值移动到第一个位置,而移动到其他位置都是不合法的。
  • (6): 复杂度: O ( m n ) O(mn) O(mn)
  • 超时DP代码:
#include<iostream>
using namespace std;

const int MAXN = 50005;
const int MAXM = 500005;
const int INF = 1<<20;

int dp[2][MAXN];
int s[MAXM], t[MAXM];

int main()
{
    int n,m;
    cin >> n >> m;
    for(int i=1; i<=m; i++)
        cin >> s[i] >> t[i];
    for(int i=1; i<=n; i++)
    {
        dp[0][i] = INF;
    }
    dp[0][1] = 0;
    for(int i=1; i<=m; i++)
    {
        for(int j=1; j<=n; j++)
        {
            if(t[i] != j)
                dp[i%2][j] = dp[(i-1)%2][j];
            else
            {
                dp[i%2][j] = dp[(i-1)%2][j];
                for(int p=s[i]; p<=t[i]; p++)
                {
                    dp[i%2][j] = min(dp[i%2][j], dp[(i-1)%2][p]+1);
                }
            }
        }
    }
    cout << dp[m%2][n] << endl;
    return 0;
}

线段树加速

线段树主要是快速进行单点更新,区间更新,单点查询,区间查询的数据结构,复杂度可以达到 O ( l o g n ) O(logn) O(logn),具体原理可以参看此博客。那么如何利用线段树加速上述的的DP呢?

  • 再次观察状态转移方程式:
    d p [ i ] [ j ] = { d p [ i − 1 ] [ j ] t [ i ] ! = j m i n { d p [ i − 1 ] [ j ] , m i n { d p [ i − 1 ] [ j ′ ] ∣ s i &lt; = j ′ &lt; = t i } } e l s e dp[i][j] = \begin{cases} dp[i-1][j] &amp; t[i] !=j \\ min\{dp[i-1][j], min\{dp[i-1][j&#x27;]|s_i &lt;=j&#x27;&lt;=t_i\}\} &amp; else \end{cases} dp[i][j]={dp[i1][j]min{dp[i1][j],min{dp[i1][j]si<=j<=ti}}t[i]!=jelse
    对于一个新机器, 我们实际上只更新了j = t[i]的状态,其他保持不变, 也就是每次只进行了单点更新以及区间查询最小值。那么结合线段树,我们就可以把复杂度降为 O ( m l o g ( n ) ) O(mlog(n)) O(mlog(n))
  • AC代码:
#include<iostream>
#include<stdio.h>
using namespace std;

const int MAX = 4 * 50000+5;
const int MAXM = 500000+5;
const int inf = 1 << 20;

int mathine_s[MAXM], mathine_e[MAXM];
struct Node
{
    int s;
    int e;
    int data; // 维护最小值.
}dp[MAX];

void build(int s, int e, int cnt)
{
    dp[cnt].s = s;
    dp[cnt].e = e;
    if(s == e-1)
    {
        if(s == 1) //相当于给dp[0][1]赋值0
            dp[cnt].data = 0;
        else
            dp[cnt].data = inf;
        return;
    }

    int mid = (s + e) >> 1;
    int left = (cnt << 1) + 1;
    int right = (cnt << 1) + 2;
    build(s, mid, left);
    build(mid, e, right);
    dp[cnt].data = min(dp[left].data, dp[right].data);
    return;
}
int query(int s, int e, int index)
{
    if(s<=dp[index].s && dp[index].e <= e)
        return dp[index].data;
    else if(s >= dp[index].e || e <= dp[index].s)
        return inf;
    int left = (index << 1) + 1;
    int right = (index << 1) + 2;
    return min(query(s,e,left), query(s,e,right));
}

void update_one(int p, int key, int index)
{
    if(p == dp[index].s && dp[index].s == dp[index].e-1)
    {
        dp[index].data = key;
        return;
    }
    int mid = (dp[index].s + dp[index].e) >> 1;
    if(p < mid)
        update_one(p, key, (index<<1)+1);
    else
        update_one(p, key, (index<<1)+2);
    dp[index].data = min(dp[(index<<1)+1].data, dp[(index<<1)+2].data);
    return;
}
int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i=1; i<=m; i++)
        scanf("%d%d", &mathine_s[i], &mathine_e[i]);
    build(0, n+1, 0);
    for(int i=1; i<=m; i++)
    {
        int v = min(query(mathine_e[i], mathine_e[i]+1, 0), query(mathine_s[i], mathine_e[i]+1, 0) + 1);
        update_one(mathine_e[i], v, 0);
    }
    int res = query(n, n+1, 0);
    printf("%d\n", res);
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值