「号爸十一集训 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;
    }
};

原文地址:https://www.cnblogs.com/handwer/p/15363759.html