「SCOI2014」方伯伯的商场之旅

时间:2022-06-01 12:35:20

「SCOI2014」方伯伯的商场之旅

题目描述

方伯伯有一天去参加一个商场举办的游戏。商场派了一些工作人员排成一行。每个人面前有几堆石子。说来也巧,位置在 \(i\) 的人面前的第 \(j\) 堆的石子的数量,刚好是 \(i\) 写成 \(K\) 进制后的第 \(j\) 位。

现在方伯伯要玩一个游戏,商场会给方伯伯两个整数 \(L,R\)。方伯伯要把位置在 \([L, R]\) 中的每个人的石子都合并成一堆石子。每次操作,他可以选择一个人面前的两堆石子,将其中的一堆中的某些石子移动到另一堆,代价是移动的石子数量\(\times\)移动的距离。商场承诺,方伯伯只要完成任务,就给他一些椰子,代价越小,给他的椰子越多。所以方伯伯很着急,想请你告诉他最少的代价是多少。

\(1≤L≤R≤10^{15}, 2≤K≤20\)

解题思路 :

首先考虑一下对于某一个数如何求出其合并成一堆的代价,观察发现,最终答案的形态一定可以转化成所有数位都移到某一位

那么问题就转化为找出一个数位,使得其他数位移动到它的总代价最小,可以直接带权中位数求解

但实际上,带权中位数在数位 \(dp\) 的过程中状态不好表示,不妨再多考虑一下子问题解法的由来

考虑一下带权中位数 \(p\) ,其必然满足 \(val_p + \sum_{i=1}^{p-1} val_i \geq \sum_{i=p+1}^{n}\) ,反之同理,也就说,其无论往左移动或者往右移动都不会更优了

我们不妨先假设初始的 \(p = 1\) ,那么就只需要考虑 \(p\) 往右移动到 \(p+1\) 新增的代价了,显然是 \(\sum_{i=1}^{p} val_i - \sum_{i=p+1}^{n} val_i\)

此时如果代价 \(< 0\) 那么说明往右移动会更优,当某一时刻代价 \(\geq 0\) 的时候说明 \(p\) 既不能往右移动也不能往左移动了,此时其是带权中位数

也就是说,我们可以让所有数先移动到 \(1\) ,然后算出每一个 \(p\) 向右移动到 \(p+1\) 的代价,然后把所有 \(<0\) 的代价加上即可

具体实现的话就是一个比较简单的数位 \(dp\) ,考虑分两步计算:

\(f[i][0/1]\) 表示前 \(i\) 个数位,之前是否是上界的前缀的数全部移到第一位的总代价,枚举数位转移即可

对于每一个 \(p\in[1,len]\) ,算出 \(g[i][sum][0/1]\) 表示前 \(i\) 个数位,满足 \(\sum_{i=1}^{p} val_i - \sum_{i=p+1}^{n} val_i = sum\) 的数的个数,也是简单的枚举数位转移即可

注意在前导 \(0\) 的处理上,前导 \(0\) 不能被计算到距离里面,可以考虑每次只做恰好等于 \(n\) 位的数,多拿几个上界做几次,也可以加细节判掉,总复杂度是 \(O(log^3nK)\)


/*program by mangoyang*/
#include<bits/stdc++.h>
#define inf (0x7f7f7f7f)
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a) < (b) ? (a) : (b))
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
template <class T>
inline void read(T &x){
    int f = 0, ch = 0; x = 0;
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
    for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
    if(f) x = -x;
}

#define int ll

const int zzd = 401;

int f[25][2], ss[25][2], g[25][1005][2], s[25], tmp[25], L, R, bs, lim, ans, ans2;

inline int Getcost(){
    int res = 0; memset(f, 0, sizeof(f)), memset(ss, 0, sizeof(ss));
    f[1][0] = s[1] - 1, f[1][1] = 1;
    for(int i = 2; i <= lim; i++){
        for(int j = 0; j < bs; j++){
            f[i][0] += f[i-1][0];
            ss[i][0] += ss[i-1][0] + f[i-1][0] * (i - 1) * j; 
        }
        for(int j = 0; j < s[i]; j++){
            f[i][0] += f[i-1][1];
            ss[i][0] += ss[i-1][1] + f[i-1][1] * (i - 1) * j;
        }
        f[i][1] += f[i-1][1];
        ss[i][1] += ss[i-1][1] + f[i-1][1] * s[i] * (i - 1);
    }
    res += ss[lim][0] + ss[lim][1];
    return res;
}

inline int solve(int num){
    lim = 0; while(num) tmp[++lim] = num % bs, num /= bs;
    for(int i = 1; i <= lim; i++) s[i] = tmp[lim-i+1];
    int cost = Getcost();
    for(int p = 1; p <= lim; p++){
        memset(g, 0, sizeof(g)), g[1][zzd-s[1]][1] = 1;
        for(int i = 1; i < s[1]; i++) g[1][zzd-i][0] = 1;
        for(int i = 2; i <= lim; i++)
            for(int j = -400; j <= 400; j++){
                int sgn = i <= p ? 1 : -1;
                for(int k = 0; k < bs; k++) g[i][j+zzd][0] += g[i-1][j+sgn*k+zzd][0];
                for(int k = 0; k < s[i]; k++) g[i][j+zzd][0] += g[i-1][j+sgn*k+zzd][1];
                g[i][j+zzd][1] += g[i-1][j+sgn*s[i]+zzd][1];
            }
        for(int j = 1; j <= 400; j++) cost -= (g[lim][j+zzd][0] + g[lim][j+zzd][1]) * j;
    }
    return cost;
}

inline int calc(int x){
    int num = x, n = 0, ans = 0;
    while(num) ++n, num /= bs;
    ans += solve(x); int base = 0;
    for(int i = 1; i < n; i++)
        base = base * bs + bs - 1, ans += solve(base);    
    return ans;
}

signed main(){
    read(L), read(R), read(bs);
    cout << calc(R) - calc(L - 1);
    return 0;
}