DP(数位进阶二)

14 篇文章 0 订阅
11 篇文章 0 订阅

题意: 给定区间, 求该区间内满足

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

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

>> face <<

Strategy: 数位dp : 首先想一个问题, 对于一个人面前的石子, 怎样抉择才是最优的? 我们考虑"1,2,3", 如果我们把石子都集中在第一堆, 代价是 2 ∗ 1 + 3 ∗ 2 = 8 2*1+3*2 = 8 21+32=8 ,然后我们考虑集中在第二堆, 代价: 1 ∗ 1 + 3 ∗ 1 = 4 1*1 + 3*1 = 4 11+31=4, 第三堆: 2 ∗ 1 + 2 ∗ 1 = 4 2*1+2*1 = 4 21+21=4 好像没啥规律, 不过可以得到启示: 当集合点从第一堆转成第二堆时, 变化可以看成 8 + 1 − 2 − 3 = 4 8+1-2-3= 4 8+123=4 , 于是可以得到推论, 从第i堆转移到第i+1堆时, 变化量是i的前缀和减去i+1的后缀和, 而且显然, 变换量是单增的(前缀和单增,后缀和单减), 我们可以贪心的判断转移的变化量是不是正数. 于是我们可以先用数位dp把在区间内所有的数集合点在个位数时的花费算出来, 然后数位dfs枚举集合点判断最优

状态: dp[i][j][k]代表搜到第i位,起点为j, 目前的代价是k的状态;

目标: s u m [ r ] − s u m [ l − 1 ] ( s u m [ i ] 代 表 1   i 内 的 所 有 合 法 数 的 个 数 ) sum[r]-sum[l-1](sum[i]代表1~i内的所有合法数的个数) sum[r]sum[l1](sum[i]1 i)

边界: 本题无

合法判断: 条件转移合法判断

转移预处理

attention: 开数组的时候注意不要开太大

双倍经验: 贪心 + 双数位 + 进制转换

  • 我觉得这题会了才能证明你学过数位dp
@author: jasonleft 记忆化数位
#include <bits/stdc++.h>
#include <bits/extc++.h>
#define _rep(i, a, b) for (ll i = (a); i <= (b); ++i)
#define _rev(i, a, b) for (ll i = (a); i >= (b); --i)
#define _for(i, a, b) for (ll i = (a); i < (b); ++i)
#define _rof(i, a, b) for (ll i = (a); i > (b); --i)
#define ll long long
#define db double
#define oo 0x3f3f3f3f
#define eps 0.00001
#define all(x) x.begin(), x.end()
#define met(a, b) memset(a, b, sizeof(a))
#define id(x) ((x + 8))
#define bin(x) cerr << #x << " is " << bitset<15>(x) << endl
#define what_is(x) cerr << #x << " is " << x << endl
#define lowbit(x) x &(-x)
using namespace std;
const ll maxn = 30;
ll l, r, cnt, k, dp[maxn][maxn][4002], a[maxn];

inline ll dfs(ll cur, ll sum, ll start, bool up)
{
	if (cur == 0)
		return sum;
	ll &t = dp[cur][start][sum];
	if (!up && ~t)
		return t;
	ll ret = 0;
	_rep(i, 0, up ? a[cur] : k - 1)
	{
		if (cur == start && i == 0)
			ret += dfs(cur - 1, sum, start - 1, up && i == a[cur]);
		else
			ret += dfs(cur - 1, sum + (cur - 1) * i, start, up && i == a[cur]);
	}
	if (!up)
		t = ret;
	return ret;
}

ll dfs1(ll cur, ll alter, ll pos, bool up)
{
	if (alter < 0)
		return 0;
	if (!cur)
		return alter;
	ll &t = dp[cur][pos][alter];
	if (!up && ~t)
		return t;
	ll ret = 0;
	_rep(i, 0, up ? a[cur] : k - 1)
	{
		if (cur >= pos)
			ret += dfs1(cur - 1, alter + i, pos, up && i == a[cur]);
		else
			ret += dfs1(cur - 1, alter - i, pos, up && i == a[cur]);
	}
	if (!up)
		t = ret;
	return ret;
}

inline ll solve(ll __)
{
	cnt = 0;
	met(dp, -1);
	while (__)
		a[++cnt] = __ % k, __ /= k;
	ll res = dfs(cnt, 0, cnt, 1);
	met(dp, -1);
	_rep(i, 2, cnt) //枚举最终优化的位置
	{
		res -= dfs1(cnt, 0, i, 1);
	}
	return res;
}

signed main()
{
	cin >> l >> r >> k;
	cout << solve(r) - solve(l - 1) << endl;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值