zoukankan      html  css  js  c++  java
  • 「号爸十一集训 Day 10.2」作业题解

    HDU 3680 Naughty Fairies (Also POJ 3278 Catch That Cow)

    解题报告

    首先 (m > n) 直接输出 (m - n),因为你只能每次做减法。

    同时这给我们提供了一条重要原则:在操作过程中不能让 (m)(n) 大太多。


    乘法是个很重要的操作,考虑转成二进制来看。

    注意到多次乘 2 就相当于在二进制后面补零,可以把数顶到前面,因此,如果 (m) 的二进制表达是 (n) 二进制表达的前缀,那是不是可以用贪心来做?每次乘 2,遇到 1 就加 1.

    似乎和减法操作没啥关系了,总感觉不对劲。


    这个贪心真的可以吗?

    很容易举出反例:

    n = (1111111)2
    m = (111)2
    
    一种更优的操作方式是 m -> 1000 -> 10000000 -> 1111111
    

    所以这给我们一个更重要的信息:(m) 的二进制表达变成 (n) 前缀 (+1) 的二进制表达,然后再减回来,似乎也是一个可行的方案。

    但是为啥不能是 (+2,+3) 甚至加更多?

    这里需要用到另一个性质:在第一个乘号之后,操作序列不可能出现连续的 (+1) 或连续的 (-1),因为 (((( imes 2) +1) + 1) Leftrightarrow ((+1) imes 2)),显然是不优的。

    (+2) 甚至更多的情况实际上还是多一位或者多几位的前缀 / 前缀 (+1) 问题。


    如果 (m) 不是前缀咋办?那就把它变成前缀。显然在第一次执行乘法操作之前,一定可以通过加法操作把它变成某一段前缀。


    所以我们可以开始 DP 了:设 f[i][0/1] 表示把 (m) 的二进制表达变成 (n) 二进制前 (i) 位的前缀(0)/ 前缀加一(1)的最小步数。

    转移需要对 (n) 当前位是 (0/1) 分别讨论。

    代码实现

    没写高精。

    int n, m;
    std::bitset<30> nn;
    
    int dp[1000000][2]; // 把 M 变成 N 的前 i 位前缀(前缀 + 1)最少步数
    
    void DP() {
        nn = n;
        int mxn = 29; while (nn[mxn] == 0) --mxn; 
        memset(dp, 0x3f, sizeof dp);
        int tmp = 0; for (int i = 0; i <= mxn; ++i) {
            tmp = tmp * 2 + nn[mxn - i];
            dp[i][0] = std::abs(m - tmp);
            dp[i][1] = std::abs(m - tmp - 1);
        }
        for (int i = 1; i <= mxn; ++i) {
            if (nn[mxn - i] == 0) {
                dp[i][0] = std::min(dp[i][0], std::min(dp[i - 1][0] + 1, dp[i - 1][1] + 2));
                dp[i][1] = std::min(dp[i][1], std::min(dp[i - 1][0] + 2, dp[i - 1][1] + 2));
            } else {
                dp[i][0] = std::min(dp[i][0], std::min(dp[i - 1][0] + 2, dp[i - 1][1] + 2));
                dp[i][1] = std::min(dp[i][1], std::min(dp[i - 1][0] + 2, dp[i - 1][1] + 1));
            }
        } cout << std::min(dp[mxn][0], dp[mxn][1] + 1) << endl;
    }
    
    int main() {
        std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
        // while (true) { 
            cin >> m >> n;
            if (m >= n) cout << m - n << endl;
            else DP();
        // }
        return 0;
    }
    

    Topcoder SRM 481 Div.1 Ticket Printers

    解题报告

    类似于《关路灯》一样的想法,装好程序的打印机一定是一个连续区间,于是就可以很轻松设出 dp 状态:f[l][r][stat][0/1] 表示当前安排了 ([l,r]) 的打印机的任务,选中数字的状态是 stat,当前位置在 (l(0)r(1)),的最小时间

    最小时间?

    最小什么时间?

    可以发现,如果我们记录“打印的最小时间”,那么我们无法得知“走路的最小时间”;反过来同理。如果我们记录“完成任务的最小时间”,那么这两个项甚至都无法转移。这是一个状态设计很模糊的 dp,它不满足最优子结构和无后效性,两种情况对后面都有影响。

    怎么办?

    ([l, r]) 的打印时间单独抽出来。换句话说,f[l][r][stat][0/1] 表示的时间是不考虑 ([l, r]) 这段打印机的打印时间,仅考虑其他打印机打印时间包括走路时间等等一大堆的时间。我们给这段打印机定完任务之后,就不管它们了,继续走,等到其他的打印机都打完了求出最短时间了,再来看这段打印机花费的时间,两个取 (max),就是总花费时间了。

    转移就枚举往左端点走还是往右端点走。

    另外,知道 (l)stat 可以求出 (r),这样空间可以省去一维。

    代码实现

    class TicketPrinters {
    public:
        static const int MAXN = 16 + 10;
    
        int n, maxstat = 0;
        int want[MAXN], start[MAXN], poss[MAXN];
    
        int dp[MAXN][(1 << (16 + 1))][2];
        bool vis[MAXN][(1 << (16 + 1))][2];
        // 走完了 [l, r],安排完了数字状态是 stat,当前在 l(0) r(1) **且不算当前打印机工作时间** 的最小总时间
        // r 可以通过 l 和 popcount 算出来
        // 每个状态的答案是它下一个状态的答案和当前打印机工作时间取 max,也就是把当前打印机时间拆出来单独考虑
    
        int search(int l, int r, int stat, int dir) {
            if (stat == maxstat) return 0;
            if (vis[l][stat][dir]) return dp[l][stat][dir];
            vis[l][stat][dir] = true; int &ans = dp[l][stat][dir] = 0x7f7f7f7f;
            int pos = dir ? r : l;
            for (int now = 1; now <= n; ++now) {
                if (stat & (1 << (now - 1))) continue;
                if (l > 1) {
                    ans = std::min(ans,
                        // !!! the dist should be calced in the state
                        poss[pos] - poss[l - 1] + std::max(
                            search(l - 1, r, stat | (1 << (now - 1)), 0),
                            std::abs(want[now] - start[l - 1]) + 1
                            // !!! Dont forget the +1
                        )
                    );
                }
                if (r < n) {
                    ans = std::min(ans, 
                        poss[r + 1] - poss[pos] + std::max(
                            search(l, r + 1, stat | (1 << (now - 1)), 1),
                            std::abs(want[now] - start[r + 1]) + 1
                        )
                    );
                }
            } return ans;
        }
    
        int minTime(int currentPrinter, std::vector <int> printerDistance, std::vector <int> startingValues, std::vector <int> wantedValues) {
            ++currentPrinter; // !!! notice indice 0 and 1
            n = wantedValues.size(); maxstat = (1 << n) - 1;
            for (int i = 1; i <= n; ++i) {
                want[i] = wantedValues[i - 1];
                start[i] = startingValues[i - 1];
                if (i != 1) poss[i] = poss[i - 1] + printerDistance[i - 1 - 1];
            } 
            for (int i = 1; i <= n; ++i) printf("%d%s", poss[i], i == n ? "
    " : " ");
            int ans = 0x7f7f7f7f;
            for (int i = 1; i <= n; ++i) {
                ans = std::min(ans,
                    std::max(
                        search(currentPrinter, currentPrinter, (1 << (i - 1)), 0),
                        std::abs(want[i] - start[currentPrinter]) + 1
                    )
                );
            } return ans;
        }
    };
    
    
  • 相关阅读:
    Ubuntu在命令行开启远程桌面
    Qt5编译项目出现GL/gl.h:No such file or directory错误
    硬盘录像机协议与技术汇总
    js判断IP字符串是否正确
    PHP获取原生POST数据
    hdu 5093 二分匹配
    hdu 4435 bfs+贪心
    hdu 4431 绝对值之和最小公式
    hdu 5073 推公式相邻质心转换
    hdu 3657 最小割(牛逼!!!!)总算理解了
  • 原文地址:https://www.cnblogs.com/handwer/p/15363759.html
Copyright © 2011-2022 走看看