[Luogu P3286] [BZOJ 3598] [SCOI2014]方伯伯的商场之旅

122 篇文章 0 订阅
8 篇文章 0 订阅
洛谷传送门
BZOJ传送门

题目描述

方伯伯有一天去参加一个商场举办的游戏。商场派了一些工作人员排成一行。每个人面前有几堆石子。

说来也巧,位置在 i i i 的人面前的第 j j j 堆的石子的数量,刚好是 i i i 写成 K K K 进制后的第 j j j 位。现在方伯伯要玩一个游戏,商场会给方伯两个整数 L , R L,R L,R

方伯伯要把位置在 [ L , R ] [L, R] [L,R] 中的每个人的石子都合并成一堆石子。每次操作,他可以选择一个人面前的两堆石子,将其中的一堆中的某些石子移动到另一堆,代价是移动的石子数量 * 移动的距离。

商场承诺,方伯伯只要完成任务,就给他一些椰子,代价越小,给他的椰子越多。所以方伯伯很着急,想请你告诉他最少的代价是多少。例如: 10 10 10 进制下的位置在 12312 12312 12312 的人,合并石子的最少代价为: 1 ∗ 2 + 2 ∗ 1 + 3 ∗ 0 + 1 ∗ 1 + 2 ∗ 2 = 9 1 * 2 + 2 * 1 + 3 * 0 + 1 * 1 + 2 * 2 = 9 12+21+30+11+22=9即把所有的石子都合并在第三堆

输入输出格式

输入格式:

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

输出格式:

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

输入输出样例

输入样例#1:
3 8 3
输出样例#1:
5

说明

1 ≤ L ≤ R ≤ 1 0 15 , 2 ≤ K ≤ 20 1 \le L \le R \le 10^{15}, 2 \le K \le 20 1LR1015,2K20

解题分析

wtcl, 这道题都想了好久…

显然还是数位 d p dp dp。 注意到对于一个有 k k k位数的 P P P, 如果把所有数位的数字数都放在第 m m m位, 那么贡献是 ∑ i = 1 k ∣ i − m ∣ × d g t [ i ] \sum_{i=1}^{k}|i-m|\times dgt[i] i=1kim×dgt[i]

那么我们可以先把所有数放在最后一位, 然后逐位向前移。 从第 i i i位移到第 i − 1 i-1 i1位的减少贡献就是前 i − 1 i-1 i1位的数位和减去后 K − i + 1 K-i+1 Ki+1位的数位和。 如果这个值小于0, 直接 r e t u r n return return就好。

由于 K ≤ 20 K\le 20 K20, 所以所有数位和不会太大, 直接设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i位数位和为 j j j 的贡献之和就好了。

代码如下:

#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <cstring>
#include <algorithm>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define ll long long
int num[70];
int K;
ll l, r;
ll dp[50][2050];
ll DFS(R int dgt, R int sum, R bool lim)
{
	if (!dgt) return sum;
	if ((!lim) && (~dp[dgt][sum])) return dp[dgt][sum];
	int up = lim ? num[dgt] : K - 1;
	ll ret = 0;
	for (R int i = 0; i <= up; ++i)
	ret += DFS(dgt - 1, sum + (dgt - 1) * i, lim & (i == up));
	if (!lim) dp[dgt][sum] = ret;
	return ret;
}
ll DFS(R int dgt, R int sum, R int pos, R bool lim)
{
	if (sum < 0) return 0;
	if (!dgt) return sum;
	if ((!lim) && (~dp[dgt][sum])) return dp[dgt][sum];
	int up = lim ? num[dgt] : K - 1;
	ll ret = 0;
	for (R int i = 0; i <= up; ++i)
	if (dgt >= pos) ret += DFS(dgt - 1, sum + i, pos, lim & (i == up));
	else ret += DFS(dgt - 1, sum - i, pos, lim & (i == up));
	if (!lim) dp[dgt][sum] = ret;
	return ret;
}
ll solve(R ll val)
{
	int cnt = 0;
	std::memset(dp, -1, sizeof(dp));
	W (val) num[++cnt] = val % K, val /= K;
	ll ret = DFS(cnt, 0, 1);
	for (R int i = 2; i <= cnt; ++i)
	std::memset(dp, -1, sizeof(dp)), ret -= DFS(cnt, 0, i, 1);
	return ret;
}
int main(void)
{
	scanf("%lld%lld%d", &l, &r, &K);
	printf("%lld", solve(r) - solve(l - 1));
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值