zoukankan      html  css  js  c++  java
  • [BZOJ3598][SCOI2014]方伯伯的商场之旅(数位DP,记忆化搜索)

    3598: [Scoi2014]方伯伯的商场之旅

    Time Limit: 30 Sec  Memory Limit: 64 MB
    Submit: 449  Solved: 254
    [Submit][Status][Discuss]

    Description

    方伯伯有一天去参加一个商场举办的游戏。商场派了一些工作人员排成一行。每个人面前有几堆石子。说来也巧,位置在 i 的人面前的第 j 堆的石子的数量,刚好是 i 写成 K 进制后的第 j 位。
    现在方伯伯要玩一个游戏,商场会给方伯伯两个整数 L,R。方伯伯要把位置在 [L, R] 中的每个人的石子都合并成一堆石子。每次操作,他可以选择一个人面前的两堆石子,将其中的一堆中的某些石子移动到另一堆,代价是移动的石子数量 * 移动的距离。商场承诺,方伯伯只要完成任务,就给他一些椰子,代价越小,给他的椰子越多。所以方伯伯很着急,想请你告诉他最少的代价是多少。
    例如:10 进制下的位置在 12312 的人,合并石子的最少代价为:
    1 * 2 + 2 * 1 + 3 * 0 + 1 * 1 + 2 * 2 = 9
    即把所有的石子都合并在第三堆

    Input

    输入仅有 1 行,包含 3 个用空格分隔的整数 L,R,K,表示商场给方伯伯的 2 个整数,以及进制数

    Output

     输出仅有 1 行,包含 1 个整数,表示最少的代价。

    Sample Input

    3 8 3

    Sample Output

    5

    HINT

     1 < =  L < =  R < =  10^15, 2 < =  K < =  20

    Source

    一眼数位DP,但是具体实现感觉非常难以理解。

    主要思路是:先默认每个人都将式子移到最低的那一位上去,然后从2开始枚举如果将其中一些人的石子从i-1移到i最终结果会优多少。

    第一个DP就是普通的数位DP,但是第二个DP就有一个很难处理的问题:如何确定到底那些人的石子可以(或需要)从i-1移到i,因为每个人最终所选的那一位都不同。

    这里有一种新型的记忆化搜索,dfs(i,sum,lim)表示(从高到低)前i位将(那些可以或需要移动的)人移动,且前i位已经变优了sum,所能得到的最终解(也就是最后最多能变优多少)。一般记搜都是形如dfs(i)+=dfs(i+1)+f(i)的(最终返回的sum),但这种记搜是dfs(i,sum,lim)+=dfs(i+1,sum+f[i],lim')(最终返回的仍然是sum,只是在递归底层被处理过的sum)。也就是说,普通记搜记录的是这个状态到目标状态(递归底层)的信息,而这种记录的是初始状态到这个状态的信息。

    这种记搜实际上不应该从DP的角度去理解,而是应该从搜索的角度,实际上就是一个记录了中间状态(sum)的搜索,在底层进行处理。

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 #define rep(i,l,r) for (int i=l; i<=r; i++)
     5 #define ll long long
     6 using namespace std;
     7 
     8 int k,num[60];
     9 ll l,r,len,f[60][9*20*20][2];
    10 
    11 ll dfs1(int pos,int sum,bool lim){
    12     if (pos>len) return sum;
    13     if (f[pos][sum][lim]!=-1) return f[pos][sum][lim];
    14     ll res=0; int end=(lim?num[pos]:k-1);
    15     rep(i,0,end) res+=dfs1(pos+1,sum+i*(pos-1),lim && (i==end));
    16     return f[pos][sum][lim]=res;
    17 }
    18 
    19 ll dfs2(int pos,int sum,int m,bool lim){
    20     if (pos>len) return max(sum,0);
    21     if (f[pos][sum][lim]!=-1) return f[pos][sum][lim];
    22     ll res=0; int end=(lim?num[pos]:k-1);
    23     rep(i,0,end) res+=dfs2(pos+1,sum+((pos<m)?-i:i),m,lim && (i==end));
    24     return f[pos][sum][lim]=res;
    25 }
    26 
    27 ll solve(ll n){
    28     len=0; while (n) num[++len]=n%k,n/=k;
    29     reverse(num+1,num+len+1);
    30     memset(f,-1,sizeof(f));
    31     ll res=dfs1(1,0,1);
    32     rep(i,2,len) memset(f,-1,sizeof(f)),res-=dfs2(1,0,i,1);
    33     return res;
    34 }
    35 
    36 int main(){
    37     freopen("bzoj3598.in","r",stdin);
    38     freopen("bzoj3598.out","w",stdout);
    39     scanf("%lld%lld%d",&l,&r,&k);
    40     printf("%lld
    ",solve(r)-solve(l-1));
    41     return 0;
    42 }
  • 相关阅读:
    [009]类型转换
    [008]new、delete及动态内存分配
    [007]操作符的求解顺序
    [010]转+修正---C++的贪吃蛇程序(未用面向对象封装)
    [006]为什么C++会被叫做是C++?
    [005]逗号表达式
    [JavaScript]转--如何让JS代码高大上
    [009]C---关于输出文本的打印问题
    [008]C---gcc环境下的一个编译器版本问题
    PlayMaker 状态机FSM重用
  • 原文地址:https://www.cnblogs.com/HocRiser/p/8646437.html
Copyright © 2011-2022 走看看