把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷3286】[SCOI2014] 方伯伯的商场之旅(数位DP)

题目链接

  • 给定一个正整数 \(k\),编号为 \(i\) 的人面前的第 \(j\) 堆石子中的石子数恰好等于 \(i\)\(k\) 进制下的第 \(j\) 位的值。
  • 把某一个人面前的第 \(x\) 堆石子完全合并入第 \(y\) 堆石子需要的代价为 \(x\) 中的石子数 \(\times|x-y|\)
  • 求将 \([L,R]\) 中所有人面前的石子分别合并为一堆所需要的最小代价和。
  • \(1\le L\le R\le10^{15}\)\(2\le k\le20\)

数位 DP 求负变化量

对于一个人,我们的策略肯定选择 石子位置的中位数 作为最终的集合位置,但这样并不方便求解。

考虑将最终的集合位置从最高位向最低位移动,在这一过程中代价的变化量逐渐增大,所以当代价变化量由负转正时就可以停止移动了,因为之后不可能再出现新的负变化量。

用数位 DP 来实现对这一过程的维护。即,初始假设最终的集合位置在最高位,并求出所有人的代价和。然后,依次枚举每一个位置 \(i\),DP 求出最终集合位置从第 \(i+1\) 位移到第 \(i\) 位的所有负变化量之和,更新答案。

具体地,对于每个 \(i\),用记忆化搜索实现,记录 \(x,s\) 两维状态表示处理完前 \(x\) 位后代价变化量为 \(s\) 即可。

代码:\(O(k\log^4V)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define V 5000
#define LL long long
using namespace std;
int k,c,a[60],g[60];LL f[60][V<<1];
I LL DP(CI x,CI s,CI fg=0)//记忆化搜索
{
	if(!x) return max(s,0);if(fg&&~f[x][s+V]) return f[x][s+V];LL t=0;
	RI lim=fg?k-1:a[x];for(RI i=0;i<=lim;++i) t+=DP(x-1,s+g[x]*i,fg||(i^lim));return fg&&(f[x][s+V]=t),t;//枚举这一位填的数
}
I LL Calc(LL n)//计算小于等于n的答案
{
	if(!n) return 0;c=0;W(n) a[++c]=n%k,n/=k;RI j;for(j=1;j<=c;++j) g[j]=c-j;memset(f,-1,sizeof(f));LL t=DP(c,0);//分解;假设最终集合位置在最高位
	for(RI i=c-1;i;memset(f,-1,sizeof(f)),t-=DP(c,0),--i) for(j=1;j<=c;++j) g[j]=j<=i?1:-1;return t;//求出最终集合位置从i+1移到i的负变化量之和
}
int main()
{
	LL L,R;return scanf("%lld%lld%d",&L,&R,&k),printf("%lld\n",Calc(R)-Calc(L-1)),0;
}
posted @ 2021-08-27 15:35  TheLostWeak  阅读(71)  评论(0编辑  收藏  举报