一道有点仙的数位dp 方伯伯的商场之旅

22 篇文章 0 订阅

link

大意:

 

 思路:

先来考虑单个数字的情况
其实首先可以将题意稍微转化一下,就是移动一个石子的代价是其移动的距离。这样的话,显然我们的策略就是对于每一个石子,一次性将其移动到正确的位置,毕竟能一步到位为什么要分两次呢。在此基础上,我们要做的就是选择一个最终的合并的位置,使得移动的总距离最小

这个的结论还是比较显然的,就是所以石子位置的中位数。

接下来看看多个数字的问题

显然我们不可能对于每一个数字都去找它的最佳位置。但是如果已经知道最佳位置的话,利用数位dp,我们可以很快知道转移到该位置的总代价。那么不妨来看看最终位置转移的时候需要改变什么。如果统计转移的代价可以接受的话,这题就有突破口了,毕竟总位数不会很多。

假设当前在位置pos,当pos+1的时候,显然总代价改变的值det就是:位置>pos的数字个数-位置<=pos的数字个数,当pos右移的时候,前者的值单调增加,后者的值单调减小,所以这个改变的值det也是单调增加的。所以我们可以得出一个递推来找到最佳位置的策略:pos从1开始向右推,如果右移一位的总代价改变值<0,也就是总代价可以变小,就右移一位,否则就break即可。

这个转移其实也不难统计,就是维护一下每一个数字在对应位置的贡献即可。这里dfs2统计的是从上一位转移过来后对总代价的减小值。如果结果大于0的话,就说明减小值为正,否则我们就让其与0取最大即可,相当于不转移。

ll dfs2(ll x,ll head,ll pos,ll sum)
{
	if(sum<0) return 0;
	if(x==0) return max(sum,0ll); 
	if(!head&&dp[x][sum]!=-1) return dp[x][sum];
	ll tot=0;
	ll lim=head?a[x]:k-1;
	for(int i=0;i<=lim;++i)
	{
		tot+=dfs2(x-1,head&&i==lim,pos,sum+((x<pos)?-i:i));
	}
	if(!head) dp[x][sum]=tot;
	return tot;
}

但是这里sum,也就是变小的贡献,在统计过程中是有可能小于0的。为了防止越界,我们可以在一开始就判断一下,如果sum<0,就return 0.合理性在于,我们的数位dp是从高位往低位做的,也就是说sum是先增后减的,如果sum已经小于0了,它也不可能再变大了。


最后,我们只要统计出转移到第一个位置的总代价,然后向右递推即可。

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const ll N=1e5+10;
const ll mod=1000007;
ll n,m,k;
ll a[60];
ll cnt=0;
ll dp[60][4010];
ll init_dfs(ll x,ll head,ll sum)
{
	if(x==0) return sum;
	if(!head&&dp[x][sum]!=-1) return dp[x][sum];
	ll tot=0;
	ll lim=head?a[x]:k-1;
	for(int i=0;i<=lim;++i)
	{
		tot+=init_dfs(x-1,head&&i==lim,sum+i*(x-1));
	}
	if(!head) dp[x][sum]=tot;
	return tot;
}
ll dfs2(ll x,ll head,ll pos,ll sum)
{
	if(sum<0) return 0;
	if(x==0) return max(sum,0ll); 
	if(!head&&dp[x][sum]!=-1) return dp[x][sum];
	ll tot=0;
	ll lim=head?a[x]:k-1;
	for(int i=0;i<=lim;++i)
	{
		tot+=dfs2(x-1,head&&i==lim,pos,sum+((x<pos)?-i:i));
	}
	if(!head) dp[x][sum]=tot;
	return tot;
}
ll f(ll x)
{
	memset(dp,-1,sizeof dp);
	cnt=0;
	while(x)
	{
		a[++cnt]=x%k;
		x/=k;
	}
	ll ans=init_dfs(cnt,1,0);
	for(int i=2;i<=cnt;++i)
	{
		memset(dp,-1,sizeof dp);
		ll det=dfs2(cnt,1,i,0);
		if(det) ans-=det;
		else break;
//		ans-=dfs2(cnt,1,i,0);
	}
	return ans;
}
void solve()
{
	cin>>n>>m>>k;
	cout<<f(m)-f(n-1)<<endl;
}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
//	ll t;cin>>t;while(t--)
	solve();
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值